【问题标题】:MySQL: Force different access_type with an index for IN(...)MySQL:使用 IN(...) 的索引强制使用不同的 access_type
【发布时间】:2018-12-18 16:58:47
【问题描述】:

我正在尝试调整一个非常简单的查询:

select * from log where user_id in (...) order by id desc limit 25

我只想显示一组不同用户 ID(大约 40 个 ID)的最后 25 个事件。此查询大约需要 50 秒才能运行(表中的记录超过 8000 万条)。

通过执行EXPLAIN format=json,我可以看到access_type 是range。经过一番探索,a了解到如果我将ID数改为9,查询规划器将使用另一种访问方式:index。

所以我假设对于大量 ID,MySQL 将在组的较小和较大 id 之间进行范围扫描,如果 ID 是“接近”的,这可能是有意义的,但情况并非总是如此。也许不知何故,这些额外的数据量在进行排序时会成为一个问题(如下面的解释计划所示)。

40 个 ID 解释

{
  "query_block": {
    "select_id": 1,
    "ordering_operation": {
      "using_filesort": true,
      "table": {
        "table_name": "log",
        "access_type": "range",
        "possible_keys": [
          "app_log_user_id"
        ],
        "key": "log_user_id",
        "used_key_parts": [
          "user_id"
        ],
        "key_length": "4",
        "rows": 6150,
        "filtered": 100,
        "index_condition": "(`app`.`log`.`user_id` in (<43 different ids from 12000 to 330000>))"
      }
    }
  }
}

9 个 ID 解释

{
  "query_block": {
    "select_id": 1,
    "ordering_operation": {
      "using_filesort": false,
      "table": {
        "table_name": "log",
        "access_type": "index",
        "possible_keys": [
          "app_log_user_id"
        ],
        "key": "PRIMARY",
        "used_key_parts": [
          "id"
        ],
        "key_length": "4",
        "rows": 6901,
        "filtered": 4552.8,
        "attached_condition": "(`app`.`log`.`user_id` in (< 9 ids from 12000 to 18000))"
      }
    }
  }
}

我做了一个实验:我将该查询分为 5 个其他子查询,只有 9 个或更少的 ID,并对所有子查询应用 UNION,最后使用 ORDER 和 LIMIT 子句。这个查询的查询计划变得有点混乱,即使有奇怪的值表明其中一个子查询的搜索行数是 86737713(我认为这是一个非常错误的估计,其他的都在 10246 左右)。你猜怎么了?查询“仅”用了 6 秒,比 50 秒好。

我不知道使用哪些策略来优化这种查询,但据我所知,如果我可以告诉优化器使用index 的acess_type 代替range,它会表现更好。这可能吗?

额外细节

  • user_id 有一个外键和一个索引。
  • 我们使用 MySQL 5.6 (InnoDB)
  • 表格有大约 80kk 行。

显示创建表

CREATE TABLE `app_log` (
  `id`              int(11)          NOT NULL AUTO_INCREMENT,
  `timestamp`       datetime         NOT NULL,
  `user_id`         int(11)          NOT NULL,
  `content_type_id` int(11)          NOT NULL,
  `object_id`       int(10) unsigned NOT NULL,
  `status`          int(11)                   DEFAULT NULL,
  `type`            int(11)                   DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `app_log_content_type_id` (`content_type_id`),
  KEY `app_log_144dd2a9` (`timestamp`),
  KEY `app_log_user_id` (`user_id`, `id`)
)
  ENGINE = InnoDB
  AUTO_INCREMENT = 108628300
  DEFAULT CHARSET = latin1

【问题讨论】:

  • 请提供SHOW CREATE TABLE log;有什么腥味。列表中的 id 是数字还是字符串;是不带引号还是带引号?
  • 请在 CREATE TABLE 中包含索引。另外,看看覆盖索引是否有帮助:dba.stackexchange.com/questions/36705/…
  • @RickJames 我添加了表定义。 ID 是数字,不涉及引号。
  • 单位的kk 是什么?
  • @N.B.这应该意味着数百万(我想非常非正式,我还是修好了)。

标签: mysql sql indexing innodb database-indexes


【解决方案1】:

可能的解释:

您使用的是什么版本的 MySQL/MariaDB?我猜你正在使用 MySQL 5.6? (您使用FORMAT=JSON 确认“至少5.6.5”。)

  • 5.6.5 引入eq_range_index_dive_limit,默认为10。
  • 5.7.4 eq_range_index_dive_limit 默认提高到 200 - 影响 IN()

可能的解决方法:

此注释可能解释了IN 列表中的 9 对 43 项。建议你玩eq_range_index_dive_limit。

琐事

KK = 千
M,致会计师 = 'mille' = 千
MM,对会计师 = 百万,一拉 KK
十万,印度人 = 100K
千万,对印度人 = 1000 万(1000 万)
十亿,对英国人过去的意思是百万;幸运的是,这种困惑似乎已经消失了。
1000 和 1024(以及 KB 与 KiB)等之间的区别,出于所有实际目的,在本论坛中可以忽略。

【讨论】:

  • MySQL 版本 5.6.34。它确实有效!从 49 秒到 748 毫秒。我更改为 200(这是新的默认值,我想这是有充分理由的)。我会监测它的副作用。还有一个问题,你怎么知道的?你还记得大量的 MySQL 参数吗?或者在尝试调整查询时我是否应该始终检查“服务器系统变量部分”? (感谢琐事信息)
  • @IsraelFonseca 我几乎参加了所有的 MySQL 会议。埋在我的记忆中的是关于使用索引与扫描相关的IN(list) 的改进。我在每个版本中添加了部分有趣的功能列表。从那里开始进行一些挖掘。 10似乎太低了。但是固定的 200 听起来也是错误的。相反,它需要一些基数、行数和值分散的函数。
  • @IsraelFonseca - 另一个注意事项:优化器使用id 来避免文件排序。但如果这 9 个项目的扫描时间很长,它会更慢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-05-23
  • 2017-05-22
  • 2015-01-28
  • 2013-06-30
  • 1970-01-01
  • 2010-10-09
  • 2011-06-17
相关资源
最近更新 更多