MySQL 在大复合主键上寻求分页

MySQL seeking pagination on big composite primary key

假设我有一个 MySQL table 定义如下:

CREATE TABLE big_table (
  primary_1 varbinary(1536),
  primary_2 varbinary(1536),
  ts timestamp(6),
  ...
  PRIMARY KEY (primary_1, primary_2),
  KEY ts_idx (ts),
)

我想实现这篇博客中描述的高效分页(求分页)posthttps://use-the-index-luke.com/sql/partial-results/top-n-queries

如果我只使用主键的第一部分,流水线执行速度很快并且符合预期:

mysql> explain  select * from big_table order by ts, primary_1 limit 5;
+----+-------------+-------------------------------------+------------+-------+---------------+--------+---------+------+------+----------+-------+
| id | select_type | table                               | partitions | type  | possible_keys | key    | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------------------------------------+------------+-------+---------------+--------+---------+------+------+----------+-------+
|  1 | SIMPLE      | big_table                           | NULL       | index | NULL          | ts_idx | 7       | NULL |    5 |   100.00 | NULL  |
+----+-------------+-------------------------------------+------------+-------+---------------+--------+---------+------+------+----------+-------+

但是,如果我将主键的第二部分添加到 ORDER BY 子句中,一切都会变慢并且开始使用文件排序:

mysql> explain  select * from big_table order by ts, primary_1, primary_2 limit 5;
+----+-------------+-------------------------------------+------------+------+---------------+------+---------+------+---------+----------+----------------+
| id | select_type | table                               | partitions | type | possible_keys | key  | key_len | ref  | rows    | filtered | Extra          |
+----+-------------+-------------------------------------+------------+------+---------------+------+---------+------+---------+----------+----------------+
|  1 | SIMPLE      | big_table                           | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 6388499 |   100.00 | Using filesort |
+----+-------------+-------------------------------------+------------+------+---------------+------+---------+------+---------+----------+----------------+

无法在复合主服务器上执行此流水线执行和排序吗?还是应该以某种特殊方式编写查询?

在没有关于 MySQL 内部如何工作的先验知识的情况下,没有理由假设 ts 上的索引可以用于 order by ts, primary_1 而无需执行附加(文件)按 primary_1 排序。想象一下,例如ts 的所有值都相同的极端情况 - 索引只会为您提供所有行,然后您必须按 primary_1.

排序

然而,MySQL 可以利用一些额外的信息:InnoDB 以包含主键列的方式存储二级索引(以便能够在 table 中找到实际行) .由于该信息无论如何都存在,因此 MySQL 可以利用它——而且确实如此,通过使用 Index Extensions。这基本上将索引 ts 扩展到索引 ts, primary_1, primary_2.

所以这个技术技巧允许您使用 ts 上的索引按 ts, primary_1, primary_2 进行排序。但既然总有一个“但是”,这里就是“但是”:

Use of index extensions by the optimizer is subject to the usual limits on the number of key parts in an index (16) and the maximum key length (3072 bytes).

ts, primary_1, primary_2 上的索引将超过 3072 字节。你可以例如也不要手动创建这样的索引。所以这个扩展不再有效,MySQL 退回到对待 ts 上的索引就像对待 ts.

上的索引一样

那么为什么它对 order by ts, primary_1 有效?好吧,即使由于这些技术原因,MySQL 无法在 ts, primary_1, primary_2 上创建内部索引,它至少可以在 ts, primary_1 上创建内部索引,而不会 运行 陷入技术问题。 MySQL 实际上并没有这样做——但是 MariaDB 开发人员实现了这个技巧,所以我假设您实际上在使用 MariaDB。尽管如此,3072 的长度限制仍然适用,因此您按两个主列的顺序仍然无效。

你能做什么?

如果您可以稍微缩短主键,索引扩展将再次起作用。长(和那种类型)的主键无论如何都不常见且不实用(不仅对于这个用例),所以也许你可以为你的 table.

找到一个不同的主键

如果这不是一个选项,您可以利用一些关于数据分布的先验知识,例如如果您知道 ts 最多有 10 个值可以相同,您可以先选择前 n+10 行(使用索引),然后仅按主键对这些行进行排序。如果您通常只显示前几页,这可能会加快您的特定情况。但是您可能想针对它提出一个单独的问题并提供具体细节。