Red*_*ing 16 mysql performance
我们在 Windows Server 2008 R2 上运行 MySQL 5.1。
我们最近一直在对我们的数据库进行一些诊断,并发现了一些我们无法解释的令人不安的伪影。当我们有需要很长时间(> 2000 毫秒)的查询时,我们添加了一些代码来记录。结果令人惊讶(并且可能是对我们僵局的解释)。
有时,通常只需要很少时间(<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 |
Run Code Online (Sandbox Code Playgroud)
索引:
+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+
| 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 | |
+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+
Run Code Online (Sandbox Code Playgroud)
我们在服务器上的表中有大约 5000 行给我们带来了麻烦。
Rol*_*DBA 14
您前两个问题(Question1,Question2)中的 UPDATE 查询通过 PRIMARY KEY 使用行级锁定访问表“people”。这是我在 2011 年 6 月 6 日上午 10:03 在问题 1 中所说的
所有事务都遍历 PRIMARY 键。由于 PRIMARY 是 InnoDB 中的聚集索引,因此 PRIMARY 键和行本身是在一起的。因此,遍历一行 and 和 PRIMARY KEY 是一回事。因此,PRIMARY KEY 上的任何索引锁也是行级锁。
还没有考虑其他一些可以将缓慢归因于索引的东西:在 InnoDB 中使用非唯一索引。InnoDB 中使用非唯一索引的每个索引查找也将每行的 rowID 附加到非唯一键。rowID 基本上来自 Clustered Index。更新非唯一索引必须始终与聚集索引交互,即使表没有主键。
另一件要考虑的事情是在索引中管理 BTREE 节点的过程。有时,它需要节点的页面拆分。非唯一索引的 BTREE 节点中的所有条目都包含非唯一字段以及聚集索引内的 rowID。为了在不影响数据完整性的情况下正确缓解此类 BTREE 页的拆分,与 rowID 关联的行必须在内部经历行级锁定。
如果'people' 表有很多非唯一索引,那么准备好在表空间中有大量的索引页以及不时有微小的行锁潜入你的身边。
还有一个不那么明显的因素:关键人口
有时,当索引被填充时,组成索引的键值可能会随着时间的推移变得不平衡,并导致 MySQL 查询优化器从键查找切换到索引扫描,最后切换到全表扫描。除非您使用新索引重新设计表以补偿键的不平衡,否则您无法控制。请提供“people”表的表结构、“people”表的计数以及“people”表的显示索引输出。
即使查询仅使用 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
Run Code Online (Sandbox Code Playgroud)
描绘事件中的序列
来自问题 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;
Run Code Online (Sandbox Code Playgroud)
这两个查询更令人困惑,因为第一个查询正在更新除 people_id 666 之外的所有内容。仅使用第一个查询就痛苦地锁定了数百行。第二个查询是更新运行 5 个事件序列的 people_id 666。第一个查询在除 people_id 666 之外的每一行上运行相同的 5 个事件序列,但 iphone_device_id 的索引在具有两个不同查询的拦截过程中。有人必须以先到先得的方式锁定 BTREE 页面。
面对冲突过程中的这两对查询,可能会在一个索引中锁定相同的 BTREE 页面,这对于 InnoDB 或任何符合 ACID 的 RDBMS 来说可能是一种令人痛苦的体验。因此,除非您可以保证查询以 AUTOCOMMIT = 1 运行或允许脏读(尽管像这样的冲突使 READ-COMMITTED 和 READ-UNCOMMITED 成为 MVCC 的噩梦),否则索引变慢是这些查询对的命运。
更新 2011-06-15 10:29
@RedBlueThing :在问题 2 的查询中,第一个查询是范围查询,因此获得了很多行锁。还要注意两个查询都试图锁定相同的空间 id 0 page no 4611 n bits 152 被锁定在 PRIMARY KEY,也就是聚集索引中。
为了确保您的应用程序至少根据您期望的一系列事件运行,您可以尝试两种不同的选项:
选项 1) 将此表转换为 MyISAM(至少在开发服务器上)。每个 UPDATE、INSERT 和 DELETE 将按照先到先得的原则强加一个全表锁。
选项 2) 尝试使用SERIALIZABLE隔离级别。这将在 SHARED 模式下锁定所有预期的行。
使用这两个替代选项,您期望的事件序列要么会中断,要么会成功。如果这两个选项都失败,那么您将需要查看您的应用程序并确定查询执行顺序的优先级。一旦确定了优先级,就可以简单地撤消这些选项(对于选项 1,返回 InnoDB,对于选项 2,返回默认隔离级别 [停止使用 SERIALIZABLE])。