LIMIT 1 很慢,针对特定的记录,使用不同的key
LIMIT 1 is very slow, for specific records, using different keys
我正在诊断间歇性慢速查询,并且在 MySQL 中发现了一个我无法解释的奇怪行为。只有在执行 LIMIT 1
.
时,它才会针对特定情况选择不同的非最佳密钥策略
Table(为简洁起见删除了一些未引用的数据列)
CREATE TABLE `ch_log` (
`cl_id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`cl_unit_id` INT(11) NOT NULL DEFAULT '0',
`cl_date` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
`cl_type` CHAR(1) NOT NULL DEFAULT '',
`cl_data` TEXT NOT NULL,
`cl_event` VARCHAR(255) NULL DEFAULT NULL,
`cl_timestamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`cl_record_status` CHAR(1) NOT NULL DEFAULT 'a',
PRIMARY KEY (`cl_id`),
INDEX `cl_type` (`cl_type`),
INDEX `cl_date` (`cl_date`),
INDEX `cl_event` (`cl_event`),
INDEX `cl_unit_id` (`cl_unit_id`),
INDEX `log_type_unit_id` (`cl_unit_id`, `cl_type`),
INDEX `unique_user` (`cl_user_number`, `cl_unit_id`)
)
ENGINE=InnoDB
AUTO_INCREMENT=419582094;
这是查询,只对一个特定的 cl_unit_id
:
运行缓慢
EXPLAIN
SELECT *
FROM `ch_log`
WHERE `ch_log_type` ='I' and ch_log_event = 'G'
AND cl_unit_id=1234
ORDER BY cl_date DESC
LIMIT 1;
id|select_type|table |type |possible_keys |key |key_len|ref|rows|Extra
1 |SIMPLE |ch_log|index|cl_type,cl_event,cl_unit_id,log_type_unit_id|cl_date|8 |\N |5295|Using where
对于 cl_unit_id
的所有其他值,它使用更快的 log_type_unit_id
键。
id|select_type|table |type|possible_keys |key |key_len|ref |rows|Extra
1 |SIMPLE |ch_log|ref |ch_log_type,ch_log_event,ch_log_unit_id,log_type_unit_id|log_type_unit_id|5 |const,const|3804|Using where; Using filesort
- 所有查询大约需要 0.01 秒。
- "slow unit"查询需要10-15分钟!
我看不出这个'unit'的数据有什么奇怪的:
- 1234单元只有6条记录类型I和事件G。
- 其他单位还有很多
- 1234单元总共只有32,000条日志,这是典型的。
- 数据本身是正常的,没有变大变旧。
- 数据库中大约有 3,000 个 "units",代表设备日志记录。 cl_unit_id是他们唯一的PK(虽然没有约束)。
一般信息
- 一共30m条记录,约12GB
- mysql 5.1.69-log
- Centos 64 位
- 数据正在逐渐变化(30m = 3个月的日志)但我不知道以前是否发生过这种情况
我尝试过的方法,"solve" 问题在于:
删除 LIMIT 1
- 查询以毫秒为单位运行,returns 数据。
更改为 LIMIT 2
或其他组合,例如2,3 - 以毫秒为单位运行。
添加索引提示 - 解决:
FROM `ch_log` USE INDEX (log_type_unit_id)
但是...我不想将其硬编码到应用程序中。
在主键上添加第二个顺序 "solves" 它:
ORDER BY cl_id, cl_date DESC
给予解释:
id|select_type|table |type|possible_keys |key |key_len|ref |rows|Extra
1 |SIMPLE |ch_log|ref |ch_log_type,ch_log_event,ch_log_unit_id,log_type_unit_id|log_type_unit_id|5 |const,const|6870|Using where
这与提示的类型略有不同,检查了更多记录 (6,000),但仍然在 10 毫秒内运行。
同样,我可以这样做,但我不喜欢使用我不理解的副作用。
所以我认为我的主要问题是:
a) 为什么只发生在 LIMIT 1
?
b) 数据本身 怎么会对关键策略产生如此大的影响?以及数据的哪个方面,从指数的数量和分布来看似乎很典型。
Mysql 将选择一个解释计划,并根据它认为在统计上是最佳选择的方式使用不同的索引。对于您所有的第一个问题,这就是答案:
- 删除
LIMIT 1
- 查询以毫秒为单位运行,returns 数据。
和 -> 是的,检查它,解释计划很好
- 更改为
LIMIT 2
或其他组合,例如2,3 - 以毫秒为单位运行。 -> 同样适用。优化器选择了一个不同的索引,因为突然之间,预期的块读取量变成了 LIMIT 1
的两倍(这只是一种可能性)
- 加个索引提示就解决了->当然,你强制一个好的解释计划
- 在主键上添加第二个order by也"solves"它->是的,因为巧合,结果是一个更好的解释计划
现在,这只回答了一半的问题。
a) why does it only happen for LIMIT 1?
它实际上发生不仅因为 LIMIT 1
,而且因为
- 您的数据统计重新分区(定向优化器的决策)
- 你的
ORDER BY DESC
子句。尝试使用 ORDER BY ... ASC
,您可能也会看到改进。
这种现象是众所周知的。请read on.
其中一个被接受的解决方案(在文章的底部)是强制索引与您所做的相同。是的,有时候,这是有道理的。否则的话,这个提示的东西早就被彻底抹杀了。机器人不可能总是完美的:-)
b) how can the data itself affect the key-strategy so much? And what
aspect of the data, seeing as the quantity and spread in the indexes
seems typical.
你说的对,差价通常是搞砸的。不仅优化器可能会根据准确的统计信息做出错误的决定,而且它也可能完全关闭,因为 table is right below 1 / 16th of the total row count...
上的增量
我正在诊断间歇性慢速查询,并且在 MySQL 中发现了一个我无法解释的奇怪行为。只有在执行 LIMIT 1
.
Table(为简洁起见删除了一些未引用的数据列)
CREATE TABLE `ch_log` (
`cl_id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`cl_unit_id` INT(11) NOT NULL DEFAULT '0',
`cl_date` DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00',
`cl_type` CHAR(1) NOT NULL DEFAULT '',
`cl_data` TEXT NOT NULL,
`cl_event` VARCHAR(255) NULL DEFAULT NULL,
`cl_timestamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`cl_record_status` CHAR(1) NOT NULL DEFAULT 'a',
PRIMARY KEY (`cl_id`),
INDEX `cl_type` (`cl_type`),
INDEX `cl_date` (`cl_date`),
INDEX `cl_event` (`cl_event`),
INDEX `cl_unit_id` (`cl_unit_id`),
INDEX `log_type_unit_id` (`cl_unit_id`, `cl_type`),
INDEX `unique_user` (`cl_user_number`, `cl_unit_id`)
)
ENGINE=InnoDB
AUTO_INCREMENT=419582094;
这是查询,只对一个特定的 cl_unit_id
:
EXPLAIN
SELECT *
FROM `ch_log`
WHERE `ch_log_type` ='I' and ch_log_event = 'G'
AND cl_unit_id=1234
ORDER BY cl_date DESC
LIMIT 1;
id|select_type|table |type |possible_keys |key |key_len|ref|rows|Extra
1 |SIMPLE |ch_log|index|cl_type,cl_event,cl_unit_id,log_type_unit_id|cl_date|8 |\N |5295|Using where
对于 cl_unit_id
的所有其他值,它使用更快的 log_type_unit_id
键。
id|select_type|table |type|possible_keys |key |key_len|ref |rows|Extra
1 |SIMPLE |ch_log|ref |ch_log_type,ch_log_event,ch_log_unit_id,log_type_unit_id|log_type_unit_id|5 |const,const|3804|Using where; Using filesort
- 所有查询大约需要 0.01 秒。
- "slow unit"查询需要10-15分钟!
我看不出这个'unit'的数据有什么奇怪的:
- 1234单元只有6条记录类型I和事件G。
- 其他单位还有很多
- 1234单元总共只有32,000条日志,这是典型的。
- 数据本身是正常的,没有变大变旧。
- 数据库中大约有 3,000 个 "units",代表设备日志记录。 cl_unit_id是他们唯一的PK(虽然没有约束)。
一般信息
- 一共30m条记录,约12GB
- mysql 5.1.69-log
- Centos 64 位
- 数据正在逐渐变化(30m = 3个月的日志)但我不知道以前是否发生过这种情况
我尝试过的方法,"solve" 问题在于:
删除
LIMIT 1
- 查询以毫秒为单位运行,returns 数据。更改为
LIMIT 2
或其他组合,例如2,3 - 以毫秒为单位运行。添加索引提示 - 解决:
FROM `ch_log` USE INDEX (log_type_unit_id)
但是...我不想将其硬编码到应用程序中。
在主键上添加第二个顺序 "solves" 它:
ORDER BY cl_id, cl_date DESC
给予解释:
id|select_type|table |type|possible_keys |key |key_len|ref |rows|Extra 1 |SIMPLE |ch_log|ref |ch_log_type,ch_log_event,ch_log_unit_id,log_type_unit_id|log_type_unit_id|5 |const,const|6870|Using where
这与提示的类型略有不同,检查了更多记录 (6,000),但仍然在 10 毫秒内运行。
同样,我可以这样做,但我不喜欢使用我不理解的副作用。
所以我认为我的主要问题是:
a) 为什么只发生在 LIMIT 1
?
b) 数据本身 怎么会对关键策略产生如此大的影响?以及数据的哪个方面,从指数的数量和分布来看似乎很典型。
Mysql 将选择一个解释计划,并根据它认为在统计上是最佳选择的方式使用不同的索引。对于您所有的第一个问题,这就是答案:
- 删除
LIMIT 1
- 查询以毫秒为单位运行,returns 数据。 和 -> 是的,检查它,解释计划很好 - 更改为
LIMIT 2
或其他组合,例如2,3 - 以毫秒为单位运行。 -> 同样适用。优化器选择了一个不同的索引,因为突然之间,预期的块读取量变成了LIMIT 1
的两倍(这只是一种可能性) - 加个索引提示就解决了->当然,你强制一个好的解释计划
- 在主键上添加第二个order by也"solves"它->是的,因为巧合,结果是一个更好的解释计划
现在,这只回答了一半的问题。
a) why does it only happen for LIMIT 1?
它实际上发生不仅因为 LIMIT 1
,而且因为
- 您的数据统计重新分区(定向优化器的决策)
- 你的
ORDER BY DESC
子句。尝试使用ORDER BY ... ASC
,您可能也会看到改进。
这种现象是众所周知的。请read on.
其中一个被接受的解决方案(在文章的底部)是强制索引与您所做的相同。是的,有时候,这是有道理的。否则的话,这个提示的东西早就被彻底抹杀了。机器人不可能总是完美的:-)
b) how can the data itself affect the key-strategy so much? And what aspect of the data, seeing as the quantity and spread in the indexes seems typical.
你说的对,差价通常是搞砸的。不仅优化器可能会根据准确的统计信息做出错误的决定,而且它也可能完全关闭,因为 table is right below 1 / 16th of the total row count...
上的增量