【问题标题】:Update large table from smaller, mission critical, table without locking small table从较小的、关键任务的表更新大表而不锁定小表
【发布时间】:2019-03-15 21:02:58
【问题描述】:

在 MySQL 中,我有两个 innodb 表,一个小型任务关键表,需要随时可供读取/写入。将此称为关键任务。我有一个更大的表(> 数百万行),称为 big_table。我需要更新 big_table,例如:

update mission_critical c, big_table b
set
b.something = c.something_else
where b.refID=c.id

此查询可能需要一个多小时,但这会在mission_critical 表上创建一个写锁。有没有办法告诉 mysql,“我不想锁定 Mission_critical”,以便可以写入该表?

我知道从交易的角度来看这并不理想。我现在能想到的唯一解决方法是制作一个小型任务关键表的副本并从中进行更新(我不在乎被锁定),但如果有办法制作,我宁愿不这样做MySQL 本身可以更优雅地处理这个问题。

锁定的不是表,而是mission_critical 中的所有记录,因为它们基本上都被更新扫描了。我不是假设这一点;症状是当用户登录到在线系统时,它会尝试更新 Mission_critical 中的 datetime 列以更新他们上次登录的时间。在上面的查询运行时,这些查询由于 Lock wait timeout exceeded 错误而死亡。如果我终止上面的查询,所有待处理的查询都会立即运行。

mission_critical.id 和 big_table.refID 都已编入索引。

每个表的创建语句的相关部分是:

关键任务:

CREATE TABLE `mission_critical` (
`intID` int(11) NOT NULL AUTO_INCREMENT,
`id` smallint(6) DEFAULT NULL,
`something_else` varchar(50) NOT NULL,
`lastLoginDate` datetime DEFAULT NULL,
PRIMARY KEY (`intID`),
UNIQUE KEY `id` (`id`),
UNIQUE KEY `something_else` (`something_else`),
) ENGINE=InnoDB AUTO_INCREMENT=1432 DEFAULT CHARSET=latin1

大表:

CREATE TABLE `big_table` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`postDate` date DEFAULT NULL,
`postTime` int(11) DEFAULT NULL,
`refID` smallint(6) DEFAULT NULL,
`something` varchar(50) NOT NULL,
`change` decimal(20,2) NOT NULL
PRIMARY KEY (`id`),
KEY `refID` (`refID`),
KEY `postDate` (`postDate`),
) ENGINE=InnoDB AUTO_INCREMENT=138139125 DEFAULT CHARSET=latin1

查询的解释是:

+----+-------------+------------------+------------+------+---------------+-------+---------+------------------------------------+------+----------+-------------+
| id | select_type |      table       | partitions | type | possible_keys |  key  | key_len |                ref                 | rows | filtered |    Extra    |
+----+-------------+------------------+------------+------+---------------+-------+---------+------------------------------------+------+----------+-------------+
|  1 | SIMPLE      | mission_critical |            | ALL  | id            |       |         |                                    |  774 |      100 | Using where |
|  1 | UPDATE      | big_table        |            | ref  | refID         | refID |       3 | db.mission_critical.something_else | 7475 |      100 |             |
+----+-------------+------------------+------------+------+---------------+-------+---------+------------------------------------+------+----------+-------------+

【问题讨论】:

  • InnoDB 不锁定表,InnoDB 锁定记录,它可能会锁定一个 foreiyn 键,另外你不应该使用旧的逗号 ansi join annymore .. 如果你的 MySQL 版本支持它使用@987654325 @ 还提供两个表的表结构SHOW CREATE TABLE table
  • “小”表有多大?
  • @Paul,小桌子大概有1000行。
  • 你有b.refID 的索引吗?您是否运行了一个测试,告诉您该表已锁定以进行更新,或者您是否在做出假设?
  • @RaymondNijland 我在问题的编辑版本中添加了额外的细节。

标签: mysql sql innodb


【解决方案1】:

我首先建议使用子查询的解决方法,在内部临时表中创建副本。但是在我的测试中,小表仍然被锁定以进行写入。所以我猜你最好的选择是手动复制。

此错误报告中描述了锁定的原因:https://bugs.mysql.com/bug.php?id=72005

Sinisa Milivojevic 在回答中写道:

update table t1,t2 ....

任何带有连接的UPDATE 都被视为多表更新。在那里面 在这种情况下,引用的表必须是读锁定的,因为行不能 在UPDATE 期间在引用表中进行更改,直到它 完成的。不能有行的并发更改,也不能有DELETE 行数,更不用说,示例感谢引用的任何 DDL 桌子。目标很简单,就是让所有表保持一致 UPDATE 完成时的内容,特别是因为多表 UPDATE 可以多次执行。

简而言之,这种行为是有充分理由的。

考虑编写 INSERT 和 UPDATE 触发器,它们将即时更新big_table。这会延迟对mission_critical 表的写入。但它可能对您来说已经足够快了,并且不再需要批量更新查询。

还要检查使用char(50) 代替varchar(50) 是否更好。我不确定,但它可能会提高更新性能,因为行大小不需要更改。我可以在测试中将更新性能提高约 50%。

【讨论】:

  • @RaymondNijland 查看 OP 的评论:“小桌子大约有 1000 行”。
  • 哦,没关系,我读错了这个问题,我认为mission_critical 的记录是 10> 百万。你从来没有看过和阅读我的评论
  • “我的“希望”是子查询将创建一个内部临时表,因此引擎不需要锁定原始表中的行。”关于临时表为什么不让你拥有带有索引的临时表(CREATE TEMPORARY TABLE)并将该临时表用作更新ID
  • @RaymondNijland 制作表格副本是我首先想到的,也是 OP 考虑的解决方法。这可能是唯一的方法,因为锁的存在是有充分理由的,即使是子查询也是如此。这使我的回答无效。
  • 是的,我认为 topicstarter 在事务之外创建自己的临时表并填充临时表并在事务中进行更新时会正常,因为这不应该锁定表中的记录mission_critical这么长..
【解决方案2】:

UPDATE 将锁定它需要更改的行。它还可以锁定这些行之后的“间隙”。

您可以在循环中使用 MySQL 事务 一次只更新 100 行

开始; 选择...更新; -- 安排让这个选择包括 100 行 更新 ...; -- 更新 100 行 提交;

【讨论】:

    【解决方案3】:

    可能值得尝试关联子查询以查看优化器是否提出不同的计划,但性能可能会更差。

    update big_table b
    set b.something = (select c.something_else from mission_critical c where b.refID = c.id)
    

    【讨论】:

      猜你喜欢
      • 2017-05-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-18
      • 1970-01-01
      • 2011-10-17
      • 2012-07-12
      • 1970-01-01
      相关资源
      最近更新 更多