4

我有一个包含大约 350 000 行的表,我最近从 MyISAM 存储引擎更改为 InnoDB。

我运行查询

UPDATE `users` SET `online` = 0

每次我的服务器启动时,使用 MyISAM 时完全没有问题。查询通常只影响几百行。查询的执行时间很慢,平均大约 1.5 秒,但我可以忍受。

现在我已更改为 InnoDB,但查询可能需要数十秒才能完成。

这是 mysql-slow.log 的一部分

# Query_time: 29.431546  Lock_time: 0.000091 Rows_sent: 0  Rows_examined: 348617
SET timestamp=1372505574;
UPDATE users SET online = 0;

这个特定的查询改变了 200 行的在线值,其他的已经是 0。

我能够通过将查询更改为

UPDATE `users` SET `online` = 0 WHERE `online` != 0

此查询耗时约 0.1 秒

现在,这是我的问题。为什么从 MyISAM 更改为 InnoDB 时时间会显着增加?

如果没有 WHERE 部分,查询怎么会这么慢?据我了解,MySQL 的查询优化器非常强大,但这表明恰恰相反。什么可能导致这个非常慢的查询执行时间?

MySQL 服务器版本为 5.5.31-0。

4

3 回答 3

3

在 InnoDB 中,update语句锁定它们扫描的每一行。这意味着要更新您的 200 行,它必须创建 350 000 个行级锁,同时保持回滚锁并将先前的值提供给正在读取已更改值的任何事务(因为该事务不是已提交且更改不是最终的)

另一方面,MyISAM 会锁定整个表。

因此,如果您需要更新所有行,锁定整个表,您将获得更好的性能(您不需要行级锁)

但更好的是,提供一个 WHERE 子句,就像你做的那样,InnoDB 将只为匹配的行获取锁(以及索引树中的一些间隙锁,但这超出了问题的范围)

于 2013-06-30T15:24:52.470 回答
2

InnoDB 实现事务语义。也就是说,它会做大量工作来向您的表格的其他读者投射您的online列值在完全相同的时刻全部变为零的错觉。如果您的客户端或服务器崩溃,它还可以自动将值回滚到之前的状态。 MyISAM 不关心这个。对于具有数十万行的表来说,这是一项繁重的工作。

它不在乎值已经为零。无论如何,它会改变它们。

当您使用该WHERE子句时,您更改的行数要少得多,因此它的事务逻辑要做的工作要少得多。

这个事务逻辑是一个关键特性。您正在以一种不完美的边缘情况方式使用它。

于 2013-06-29T11:51:25.667 回答
1

您最好将查询重写为 from

UPDATE `users` SET `online` = 0 WHERE `online` != 0

UPDATE `users` SET `online` = 0 WHERE `online` = 1

使用 = vs != 时可以更好地查找索引

检查http://sqlfiddle.com/#!2/31088/4 并在此处打开“查看执行计划”以查看此处的操作

!= triggers a range 
= triggers a ref which should perform better

作为旁注,您通常不应索引选择性差的列

于 2013-08-16T15:23:05.440 回答