LIMIT 1非常慢,对于特定记录,使用不同的键

sci*_*lot 17 mysql sql optimization performance innodb

我正在诊断一个间歇性的慢查询,并在MySQL中发现了一个奇怪的行为我无法解释.它只针对一个特定情况选择不同的非最佳关键策略,只有在做一个时LIMIT 1.

表(为简洁起见,删除了一些未引用的数据列)

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;
Run Code Online (Sandbox Code Playgroud)

这是查询,对于一个特定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;
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)

对于它的所有其他值,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
Run Code Online (Sandbox Code Playgroud)
  • 所有查询大约需要0.01秒.
  • "慢单位"查询需要10-15分钟!

我看不出这个'单位'的数据有什么奇怪之处:

  • 单元1234只有6个类型I和事件G的记录.
  • 其他单位还有更多.
  • 单元1234总共只有32,000个日志,这是典型的.
  • 数据本身是正常的,没有更大或更大.
  • 数据库中有大约3,000个"单位",表示设备记录内容.cl_unit_id是它们唯一的PK(尽管没有约束).

基本信息

  • 总共有30万条记录,大约12GB
  • mysql 5.1.69-log
  • Centos 64bit
  • 数据正在逐渐变化(30m = 3个月的日志),但我不知道之前是否发生了这种情况

我尝试过的东西,可以用以下方法解决问题:

  1. 删除LIMIT 1- 查询以毫秒为单位运行并返回数据.

  2. 更改为LIMIT 2或其他组合,例如2,3 - 以毫秒为单位运行.

  3. 添加索引提示 - 解决它:

    FROM `ch_log` USE INDEX (log_type_unit_id)
    
    Run Code Online (Sandbox Code Playgroud)

    但是......我不想将其硬编码到应用程序中.

  4. 在主键上添加第二个订单也"解决"它:

    ORDER BY cl_id, cl_date DESC 
    
    Run Code Online (Sandbox Code Playgroud)

    给出解释:

    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
    
    Run Code Online (Sandbox Code Playgroud)

    这与提示的类型略有不同,检查了更多记录(6,000)但仍然以10毫秒运行.

可以这样做,但我不喜欢使用我不明白的副作用.

所以我认为我的主要问题是:

a)为什么只会发生LIMIT 1

b)数据本身如何影响关键策略?数据的哪个方面,看起来像索引中的数量和传播似乎是典型的.

Seb*_*bas 10

Mysql将选择一个解释计划并使用不同的索引,这取决于它认为在统计上是最佳选择.对于您的所有第一个问题,这就是答案:

  1. 删除LIMIT 1- 查询以毫秒为单位运行并返回数据. - >是的,检查一下,解释计划是好的
  2. 更改为LIMIT 2或其他组合,例如2,3 - 以毫秒为单位运行. - >同样适用.优化器选择不同的索引,因为突然,预期的块读取变为两倍LIMIT 1(这只是一种可能性)
  3. 添加索引提示解决了它 - >当然,您强制执行一个很好的解释计划
  4. 在主键上添加第二个订单也"解决"它 - >是,因为巧合,结果是一个更好的解释计划

现在,这只回答了一半的问题.

a)为什么它只发生在LIMIT 1?

它实际上不仅仅是因为LIMIT 1,而是因为

  • 您的数据统计重新分区(定位优化程序的决策)
  • 你的ORDER BY DESC条款.试试看,ORDER BY ... ASC你也可能会看到改进.

这种现象是完全已知的.请继续阅读.

其中一个可接受的解决方案(文章的下方)是以与您相同的方式强制索引.是的,有时,这是合理的.否则,很久以前就会彻底消除这种暗示.机器人不可能永远完美:-)

b)数据本身如何影响关键策略?数据的哪个方面,看起来像索引中的数量和传播似乎是典型的.

你说过,传播通常是乱搞.不仅优化器可能只是使用准确的统计信息做出错误的决定,但它也可能完全关闭,因为表上的增量恰好低于总行数的1/16 ...

  • 改为运行`ANALYZE`语句(这是由`show table`语句触发的语句).但是你应该调查此时统计数据是否过时是否正常.看起来你一路走来都不走运,因为它应该在你的日志内容的1/16发生变化后自动发生 (2认同)