簡體   English   中英

MySQL 分區和臨時表

[英]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;

我想從 dev.MySQL.com 閱讀以下鏈接

您不能使用 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM