[英]MySQL partitioning and temporary tables
一個大表(約 1050 萬行)最近引起了問題。 我之前修改了我的應用程序以使用臨時表進行更快的選擇,但由於 UPDATE 語句仍然存在問題。 今天我實現了分區,以便更快地進行寫入,但現在我的臨時表錯誤。 其目的是對事件進行分組,將集合的第一個事件 ID 放在 EVENT_ID 列中。 示例:從 1000 開始編寫 4 個事件將導致事件 1000、1001、1002、1003,所有事件的 EVENT_ID 都為 1000。我試圖取消 UPDATE 語句,但這需要太多重構,所以它不是一個選項。 這是表定義:
CREATE TABLE `all_events` (
`ID` bigint NOT NULL AUTO_INCREMENT,
`EVENT_ID` bigint unsigned DEFAULT NULL,
`LAST_UPDATE` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`EMPLOYEE_ID` int unsigned NOT NULL,
`QUANTITY` float unsigned NOT NULL,
`OPERATORS` float unsigned NOT NULL DEFAULT '0',
`SECSEARNED` decimal(10,2) unsigned NOT NULL DEFAULT '0.00' COMMENT 'for all parts in QUANTITY',
`SECSBURNED` decimal(10,2) unsigned NOT NULL DEFAULT '0.00',
`YR` smallint unsigned NOT NULL DEFAULT (year(curdate())),
PRIMARY KEY (`ID`,`YR`),
KEY `LAST_UPDATE` (`LAST_UPDATE`),
KEY `EMPLOYEE_ID` (`EMPLOYEE_ID`),
KEY `EVENT_ID` (`EVENT_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=17464583 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
/*!50100 PARTITION BY RANGE (`YR`)
(PARTITION p2015 VALUES LESS THAN (2016) ENGINE = InnoDB,
PARTITION p2016 VALUES LESS THAN (2017) ENGINE = InnoDB,
PARTITION p2017 VALUES LESS THAN (2018) ENGINE = InnoDB,
PARTITION p2018 VALUES LESS THAN (2019) ENGINE = InnoDB,
PARTITION p2019 VALUES LESS THAN (2020) ENGINE = InnoDB,
PARTITION p2020 VALUES LESS THAN (2021) ENGINE = InnoDB,
PARTITION p2021 VALUES LESS THAN (2022) ENGINE = InnoDB,
PARTITION p2022 VALUES LESS THAN (2023) ENGINE = InnoDB,
PARTITION p2023 VALUES LESS THAN MAXVALUE ENGINE = InnoDB) */
現在在我的應用程序中運行報告時的語句:
CREATE TEMPORARY TABLE IF NOT EXISTS ape ENGINE=MEMORY AS
SELECT * FROM all_events
WHERE LAST_UPDATE BETWEEN '2022-05-01 00:00:00' AND CURRENT_TIMESTAMP()
產生錯誤: 'Specified storage engine' is not supported for default value expressions.
有沒有辦法仍然使用帶有ENGINE=MEMORY
的臨時表,或者我可以使用另一個高性能引擎嗎? 該語句一直有效,直到實現分區。 由於 MySQL 的實現,InnoDB 是我的表可以使用的唯一引擎,並且在分區之前它一直是 InnoDB。
編輯:刪除ENGINE=MEMORY
時它確實有效,但運行SHOW CREATE TABLE
告訴我它正在使用 InnoDB。 我更喜歡 MEMORY 與 InnoDB 的性能提升。
第二次編輯: MySQL 服務器每天崩潰 2 到 3 次,每次我發現它時都會發現這個錯誤:
TRANSACTION 795211228, ACTIVE 0 sec fetching rows
mysql tables in use 13, locked 13
LOCK WAIT 866 lock struct(s), heap size 106704, 4800 row lock(s), undo log entries 1
MySQL thread id 5032986, OS thread handle 140442167994112, query id 141216988 myserver 192.168.1.100 my-user Searching rows for update
UPDATE `all_events` SET `EVENT_ID`=LAST_INSERT_ID() WHERE `EVENT_ID` IS NULL
RECORD LOCKS space id 30558 page no 16 n bits 792 index EVENT_ID of table `mydb`.`all_events` trx id 795211228 lock_mode X
它正在運行具有 3 個節點的 Galera Cluster。 節點 3 是主要的,變得不可用,並且 1 脫機以重新同步 3。我故障轉移到 2,在它趕上之前我們通常都很好,但這會導致停機。 我使用的臨時表是為了更快地讀取,分區是我提高寫入性能的嘗試。
第三次編輯:添加示例 SELECT - 注意表定義中沒有字段,為了帖子的簡單性,我減少了顯示的內容,但 SELECT 中的所有字段實際上都存在。
CREATE TEMPORARY TABLE IF NOT EXISTS allpe AS
SELECT * FROM all_events
WHERE LAST_UPDATE BETWEEN ? AND ?;
CREATE TEMPORARY TABLE IF NOT EXISTS ap1 AS SELECT * FROM allpe;
CREATE TEMPORARY TABLE IF NOT EXISTS ap2 AS SELECT * FROM allpe;
SELECT PART_NUMBER, WORKCENTER_NAME, SUM(SECSEARNED) AS EARNED, SUM(SECSBURNED) AS BURNED, SUM(QUANTITY) AS QUANTITY, (
SELECT SUM(ap1.SECSEARNED)
FROM ap1
WHERE ap1.PART_NUMBER = ape.PART_NUMBER AND ap1.WORKCENTER_ID = ape.WORKCENTER_ID
) AS EARNEDALL, (
SELECT SUM(ap2.SECSBURNED)
FROM ap2
WHERE ap2.PART_NUMBER = ape.PART_NUMBER AND ap2.WORKCENTER_ID = ape.WORKCENTER_ID
) AS BURNEDALL
FROM allpe ape
WHERE EMPLOYEE_ID = ?
GROUP BY PART_NUMBER, WORKCENTER_ID, WORKCENTER_NAME, EMPLOYEE_ID
ORDER BY EARNED;
DROP TEMPORARY TABLE allpe;
DROP TEMPORARY TABLE ap1;
DROP TEMPORARY TABLE ap2;
第四次編輯:在存儲過程中寫入 - 這不是循環,但多行可以來自多個連接到employee_presence,所以我無法獲取 ID 並將其存儲以編寫后續行。
INSERT INTO `all_events`(`EVENT_ID`,`LAST_UPDATE`,`PART_NUMBER`, `WORKCENTER_ID`,`XPPS_WC`, `EMPLOYEE_ID`,`WORKCENTER_NAME`, `QUANTITY`, `LEVEL_PART_NUMBER`,`OPERATORS`,`SECSEARNED`,`SECSBURNED`)
SELECT NULL,NOW(),NEW.PART_NUMBER,NEW.ID,OLD.XPPS_WC,ep.EMPLOYEE_ID,NEW.NAME,(NEW.PARTS_MADE-OLD.PARTS_MADE)*WorkerContrib(ep.EMPLOYEE_ID,OLD.ID),IFNULL(NEW.LEVEL_PART_NUMBER,NEW.PART_NUMBER),WorkerCount(NEW.ID)*WorkerContrib(ep.EMPLOYEE_ID,OLD.ID),WorkerContrib(ep.EMPLOYEE_ID,OLD.ID)*CreditSeconds,WorkerCount(NEW.ID)*WorkerContrib(ep.EMPLOYEE_ID,OLD.ID)*IFNULL(TIMESTAMPDIFF(SECOND, GREATEST(NEW.LAST_PART_TIME,NEW.JOB_START_TIME), now()),0)
FROM employee_presence ep WHERE ep.WORKCENTER_ID=OLD.ID;
UPDATE `all_events` SET `EVENT_ID`=LAST_INSERT_ID() WHERE `WORKCENTER_ID`=NEW.ID AND `EVENT_ID` IS NULL;
您不能使用 CREATE TEMPORARY TABLE ... LIKE 根據駐留在 mysql 表空間、InnoDB 系統表空間 (innodb_system) 或通用表空間中的表的定義來創建空表。 這種表的表空間定義包括一個TABLESPACE屬性,定義了表所在的表空間,前面提到的表空間不支持臨時表。 要根據此類表的定義創建臨時表,請改用以下語法:
創建臨時表 new_tbl SELECT * FROM orig_tbl LIMIT 0;
因此,您的案例的正確語法似乎是:
CREATE TEMPORARY TABLE ape
SELECT * FROM all_events
WHERE...
在當前問題中,有問題的列是YR smallint unsigned NOT NULL DEFAULT (year(curdate()))
。 對於在分區表達式中使用的列,此 DEFAULT 值是不合法的。 錯誤將是“不允許(子)分區函數中的常量、隨機或時區相關的表達式......”。
只有當您通過刪除分區來解決此問題時,您才會收到錯誤“默認值表達式不支持'指定的存儲引擎'”。
CREATE TABLE .. SELECT
從源表繼承主列屬性。
在當前問題中,有問題的列再次是YR smallint unsigned NOT NULL DEFAULT (year(curdate()))
。 temptable 中的列必須繼承主要屬性,包括 DEFAULT 表達式 - 但MEMORY
引擎不允許使用此表達式。
正如錯誤所暗示的,表達式 default 不適用於 MEMORY 存儲引擎。
一種解決方案是從您的all_events.yr
列中刪除該默認值。
另一種解決方案是最初創建一個空的臨時表作為 InnoDB 表,然后使用 ALTER TABLE 刪除表達式默認值並轉換為 MEMORY 引擎,然后再填充數據。
例子:
mysql> create temporary table t as select * from all_events where false;
mysql> alter table t alter column yr drop default, engine=memory;
mysql> insert into t select * from all_events;
充足的? 如果我沒記錯的話,這相當於您的SELECT
找到的(不需要臨時表):
SELECT PART_NUMBER, WORKCENTER_ID, WORKCENTER_NAME, EMPLOYEE_ID,
SUM(SECSEARNED) AS TOT_EARNED,
SUM(SECSBURNED) AS TOT_BURNED,
SUM(QUANTITY) AS TOT_QUANTITY
FROM all_events
WHERE EMPLOYEE_ID = ?
AND LAST_UPDATE >= '2022-05-01'
GROUP BY PART_NUMBER, WORKCENTER_ID, WORKCENTER_NAME;
為了性能,它需要這個。
INDEX(EMPLOYEE_ID, LAST_UPDATE)
此外,刪除分區可能會加快速度。
else (關於您所采用路徑的其他修復的注釋)
由於不需要yr
,因此通過將 '*' 更改為所需列的列表來避免它
CREATE TEMPORARY TABLE IF NOT EXISTS ape ENGINE=MEMORY AS
SELECT * FROM all_events
WHERE LAST_UPDATE BETWEEN '2022-05-01 00:00:00' AND CURRENT_TIMESTAMP()
其中 ap2.PART_NUMBER = ape.PART_NUMBER 和 ap2.WORKCENTER_ID = ape.WORKCENTER_ID
將此復合索引添加到all_events
:
INDEX(PART_NUMBER, WORKCENTER_ID)
在沒有臨時表的情況下,這可能足以使查詢足夠快。 Also add that
allpe`。
如果您正在運行 MySQL 8.0,則可以使用WITH
而不是需要兩個額外的臨時表。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.