繁体   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