偶尔查询缓慢的原因?


16

我们在Windows Server 2008 R2上运行MySQL 5.1。

我们最近在数据库上进行了一些诊断,发现了一些无法解释的令人不安的工件。当查询花费很长时间(> 2000ms)时,我们添加了一些代码来记录日志。结果令人惊讶(并且可能解释了我们的僵局)。

有时,查询通常花费很少的时间(<10毫秒),而这需要4到13秒。需要明确的是,这些查询持续不断地运行(每秒几次),并且不受这些查询时间尖峰的影响。

我们已经通过索引查找任何明显的错误,并且运气不高。

更新资料

人员表:

| people | CREATE TABLE `people` (
`people_id` bigint(20) NOT NULL AUTO_INCREMENT,
`company_id` bigint(20) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`temp_password` varchar(10) DEFAULT NULL,
`reset_password_hash` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`phone` varchar(32) DEFAULT NULL,
`mobile` varchar(32) DEFAULT NULL,
`iphone_device_id` varchar(160) DEFAULT NULL,
`iphone_device_time` datetime DEFAULT NULL,
`last_checkin` datetime DEFAULT NULL,
`location_lat` double DEFAULT NULL,
`location_long` double DEFAULT NULL,
`gps_strength` smallint(6) DEFAULT NULL,
`picture_blob_id` bigint(20) DEFAULT NULL,
`authority` int(11) NOT NULL DEFAULT '0',
`active` tinyint(1) NOT NULL DEFAULT '1',
`date_created` datetime NOT NULL,
`last_login` datetime NOT NULL,
`panic_mode` tinyint(1) NOT NULL DEFAULT '0',
`battery_level` double DEFAULT NULL,
`battery_state` varchar(32) DEFAULT NULL,
PRIMARY KEY (`people_id`),
KEY `email` (`email`),
KEY `company_id` (`company_id`),
KEY `iphone_device_id` (`iphone_device_id`),
KEY `picture_blob_id` (`picture_blob_id`),
CONSTRAINT `people_ibfk_1` FOREIGN KEY (`company_id`) REFERENCES `companies` (`company_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `people_ibfk_2` FOREIGN KEY (`picture_blob_id`) REFERENCES `blobs` (`blob_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=4658 DEFAULT CHARSET=utf8 |

索引:

+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+
| Table  | Non_unique | Key_name         | Seq_in_index | Column_name      | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+
| people |          0 | PRIMARY          |            1 | people_id        | A         |        3502 |     NULL | NULL   |      | BTREE      |         |
| people |          1 | email            |            1 | email            | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
| people |          1 | company_id       |            1 | company_id       | A         |        3502 |     NULL | NULL   |      | BTREE      |         |
| people |          1 | iphone_device_id |            1 | iphone_device_id | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
| people |          1 | picture_blob_id  |            1 | picture_blob_id  | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+

我们服务器上的表中有约5000行,这给我们带来了麻烦。


1
前两个问题中尚未显示某些内容。请在此问题中添加以下三(3)点:1)显示创建人员表\ G 2)显示人员的索引;3)从人中选择COUNT(1);
RolandoMySQLDBA 2011年

@RolandoMySQLDBA明天上班后,我会尽快这样做。干杯:)
RedBlueThing 2011年

我更新了答案。请阅读 !!!
RolandoMySQLDBA 2011年

@RolandoMySQLDBA谢谢:)。仍在解析这些东西。我会让你知道我们如何。
RedBlueThing 2011年

Answers:


14

您前两个问题(Question1Question2)中的UPDATE查询正在通过具有行级锁定的PRIMARY KEY命中表“ people”。这是我在2011年6月6日上午10:03在Question1中所说的

所有事务都遍历PRIMARY密钥。由于PRIMARY是InnoDB中的聚集索引,因此PRIMARY键和行本身在一起。因此,遍历行和PRIMARY KEY是相同的。因此,PRIMARY KEY上的任何索引锁也都是行级锁。

还没有考虑到其他可能导致索引缓慢的原因:在InnoDB中使用NON-UNIQUE索引。InnoDB中使用非唯一索引的每个索引查找也将每行的rowID附加到非唯一键。rowID基本从聚簇索引开始。即使表没有主键,更新非唯一索引也必须始终与聚簇索引交互。

要考虑的另一件事是在索引中管理BTREE节点的过程。有时,它需要节点的页面拆分。非唯一索引的BTREE节点中的所有条目均包含非唯一字段以及聚集索引内的rowID。为了在不干扰数据完整性的情况下适当减轻此类BTREE页面的分裂,与rowID关联的行必须在内部经历行级别的锁定。

如果“ people”表具有很多非唯一索引,请准备在表空间中拥有大量索引页,并时不时冒出很小的小行锁。

还有另一个不太明显的因素:关键人群

有时,当填充索引时,组成索引的键值可能会随着时间的推移而变得不平衡,并导致MySQL查询优化器从键查找,索引扫描以及最终全表扫描切换。除非您用新的索引重新设计表以补偿键的不正确性,否则您将无法控制。请提供“人”表的表结构,“人”表的计数以及“人”表的显示索引输出

即使查询仅使用PRIMARY KEY,非唯一索引中键的不平衡性仍需要BTREE平衡和页面拆分才能发生。由于您不希望发生间歇性的行级别锁定,因此此类BTREE管理将产生显着的速度降低。

更新2011-06-14 22:19

问题1的查询

UPDATE people SET company_id = 1610, name = '<name>', password = '<hash>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@yahoo.com',
phone = NULL, mobile = '<phone>', iphone_device_id = 'android:<id>-<id>',
iphone_device_time = '2011-06-06 05:35:09', last_checkin = '2011-06-06 05:24:42',
location_lat = <lat>, location_long = -<lng>, gps_strength = 3296,
picture_blob_id = 1190,
authority = 1, active = 1, date_created = '2011-04-13 20:21:20',
last_login = '2011-06-06 05:35:09', panic_mode = 0,
battery_level = NULL, battery_state = NULL WHERE people_id = 3125

UPDATE people SET company_id = 1610, name = '<name>', password = '<hash>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@yahoo.com',
phone = NULL, mobile = '<phone>', iphone_device_id = 'android:<id>-<id>-<id>-<id>',
iphone_device_time = '2011-06-06 05:24:42', last_checkin = '2011-06-06 05:35:07',
location_lat = <lat>, location_long = -<lng>, gps_strength = 3296,
picture_blob_id = 1190,
authority = 1, active = 1, date_created = '2011-04-13 20:21:20',
last_login = '2011-06-06 05:35:09', panic_mode = 0,
battery_level = NULL, battery_state = NULL WHERE people_id = 3125

描绘事件的顺序

  1. 通过PRIMARY KEY查找行
  2. 锁定行和聚集索引
  3. 为所有要更新的列创建MVCC数据
  4. 索引了四列(电子邮件,company_id,iphone_device_id,picture_blob_id)
  5. 每个索引都需要BTREE管理
  6. 在同一个事务空间内,尝试在同一行上重复步骤1-5,更新相同的列(在两个查询中发送相同的电子邮件,在两个查询中发送相同的company_id,在两个查询中发送相同的picture_blob_id,iphone_device_id不同)

问题2的查询

UPDATE people SET iphone_device_id=NULL
WHERE iphone_device_id='iphone:<device_id_blah>' AND people_id<>666;

UPDATE people SET company_id = 444, name = 'Dad', password = '<pass>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@gmail.com',
phone = NULL, mobile = NULL, iphone_device_id = 'iphone:<device_id_blah>',
iphone_device_time = '2011-06-06 19:12:29', last_checkin = '2011-06-07 02:49:47',
location_lat = <lat>, location_long = <lng>, gps_strength = 66,
picture_blob_id = 1661,
authority = 1, active = 1, date_created = '2011-03-20 19:18:34',
last_login = '2011-06-07 11:15:01', panic_mode = 0, battery_level = 0.55,
battery_state = 'unplugged' WHERE people_id = 666;

这两个查询更加令人困惑,因为第一个查询正在更新除people_id 666之外的所有内容。仅第一个查询就很痛苦地锁定了数百行。第二个查询是更新运行5个事件序列的people_id 666。第一个查询在除People_id 666以外的每一行上都运行相同的5个事件序列,但是iphone_device_id的索引处于截然不同的过程中,具有两个不同的查询。有人必须以先到先得的方式锁定BTREE页面。

面对冲突过程中的这两对查询以可能将一个索引内的相同BTREE页锁定在一个冲突索引中,对于InnoDB或任何符合ACID的RDBMS来说,都是一种痛苦的经历。因此,除非您可以保证查询以AUTOCOMMIT = 1或通过允许脏读来运行(尽管这样的冲突使MVCC进行READ-COMMITTED和READ-UNCOMMITED冲突),否则索引变慢是这些查询对的命运。

更新2011-06-15 10:29

@RedBlueThing:在问题2的查询中,第一个查询是范围查询,因此获得了很多行锁。还要注意,两个查询都试图锁定相同的空间ID 0页号4611 n位152被锁定在PRIMARY KEY中,也就是聚簇索引。

为了确保您的应用程序至少能够根据您期望的一系列事件运行,可以尝试以下两种不同的方法:

选项1)将此表转换为MyISAM(至少在开发服务器上)。每个UPDATE,INSERT和DELETE都将基于先到先得的原则强加全表锁定。

选项2)尝试使用SERIALIZABLE隔离级别。这会将所有预期的行锁定为“共享”模式。

使用这两个替代选项,您期望的事件序列将中断或成功。如果这两个选项都失败,那么您将需要查看您的应用并确定查询执行的优先级。一旦确定了优先级,就可以简单地撤消这些选项(对于选项1,返回到InnoDB,对于选项2,返回到默认隔离级别[停止使用SERIALIZABLE])。


@RolandoMySQLDBA我用您要求的详细信息更新了我们的问题。
RedBlueThing 2011年

@RolandoMySQLDBA感谢您再次关注此内容。我想知道,您对问题2的评论为何第一个查询会锁定数百行?它不会只锁定与设备ID匹配的非666行吗?(即单行)
RedBlueThing 2011年

@RolandoMySQLDBA根据您对问题1的建议,我们检查了自动提交设置并确认它已打开。
RedBlueThing 2011年

@RolandoMySQLDBA第一个问题的查询是否存在特定问题(除了更新该行中的所有字段)。是什么可以解释查询的13秒执行时间?我感觉到不建议您为四个列建立索引,但这会导致如此糟糕的性能吗?
RedBlueThing 2011年

@RolandoMySQLDBA +1,感谢您的所有建议。我们最终没有更改隔离级别来解决此问题。相反,我们对问题2进行了部分字段更新,并优化了更新路径中的查询。瞧!没有更多的僵局。:)
RedBlueThing 2011年

3

显示变量,如“ innodb%”;-特别是,如果数据和索引尚未达到缓冲池的大小,则可能比以前更难打磁盘。I / O是最大的性能杀手。

您的大多数字段是所需字段的两倍。BIGINT(8字节)对于大多数ID来说是过大的手段。5000行仅需要一个SMALLINT UNSIGNED(限制为65K,仅2个字节)。或使用MEDIUMINT来确保安全。

DOUBLE以8字节为代价为您提供16位有效数字。battery_level的精度是否超过2个有效数字?FLOAT占用4个字节。

我的意思是“更小->更可缓存->更快”。

请向我们显示缓慢的查询;至少其中一些突然变慢了。没有它们,我们只能猜测。打开慢速日志并设置long_query_time = 1; 这些将有助于找到最慢的查询。

您了解“复合”索引的好处吗?

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.