MySQL:使用 IN(...) 索引强制不同的 access_type

标签 mysql sql indexing innodb database-indexes

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

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

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

通过执行 EXPLAIN format=json我可以看到 access_typerange 。经过一番探索,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 秒要好。

我不知道使用哪些策略来优化此类查询,但根据我的浅薄知识,似乎如果我可以告诉优化器使用acess_typeindex相反 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

最佳答案

可能的解释:

您使用的 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,对会计师=百万,la KK
十万,对印度人来说 = 10 万
克罗尔,印第安人 = 10M(一千万)
十亿,对英国人来说曾经意味着一百万;幸运的是,这种困惑似乎已经消失了。
出于所有实际目的,本论坛中可以忽略 1000 和 1024(以及 KB 与 KiB)等之间的区别。

关于MySQL:使用 IN(...) 索引强制不同的 access_type,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51272141/

相关文章:

python - 如何在第一次出现数字之前的最后一个字母处截断字符串?

php - 在 PHP/MySQL 中实现递归注释

php - 从 SQL DB 添加和/或删除记录

mysql - mysql删除重复项(两个参数)

mysql - 如何将此 ActiveRecord 查询重写为 MySQL?

MySQL处理函数时的索引问题

php - MySQL:将分钟转换为小时和分钟

mysql - 如何将数据从一个表复制到另一个 "EXCEPT"一个字段

mysql - 如何使用MySQL优化表是否包含10000个条目的查询?

mysql - 如果列被重命名,MySQL 是否会重建列的索引?