【问题标题】:Indexing for keyset pagination in mysqlmysql中键集分页的索引
【发布时间】:2019-07-26 16:25:03
【问题描述】:

我正在尝试在 mysql 中建立一个索引来支持键集分页查询。我的查询如下所示:

SELECT * FROM invoice 
  WHERE company_id = 'someguid' 
    AND id > 'lastguidfromlastpage' 
  ORDER BY id
  LIMIT 10

这方面的常识表明,company_id 上的索引将包含表的主键 (id)。因此,我希望能够直接使用索引中的行,而无需查询首先对结果进行排序,但是我的解释计划显示了文件排序和索引合并:

mysql> explain SELECT *
-> FROM invoice
-> WHERE company_id = '37687714-2e9d-4daa-aee6-f7d56962f903'
->   AND id > '525ae038-0cc3-4f9a-85e6-6f36d43fae40'
-> ORDER BY id
-> LIMIT 10;
+----+-------------+---------+------------+-------------+-----------------------------+-----------------------------+---------+------+------+----------+---------------------------------------------------------------------------+
| id | select_type | table   | partitions | type        | possible_keys               | key                         | key_len | ref  | rows | filtered | Extra                                                                     |
+----+-------------+---------+------------+-------------+-----------------------------+-----------------------------+---------+------+------+----------+---------------------------------------------------------------------------+
|  1 | SIMPLE      | invoice | NULL       | index_merge | PRIMARY,invoice__company_id | invoice__company_id,PRIMARY | 76,38   | NULL |   48 |   100.00 | Using intersect(invoice__company_id,PRIMARY); Using where; Using filesort |
+----+-------------+---------+------------+-------------+-----------------------------+-----------------------------+---------+------+------+----------+---------------------------------------------------------------------------+
1 row in set, 1 warning (0.00 sec)

如果我明确地将 id 添加到索引中,那么我会得到我期望的解释计划:

mysql> explain SELECT *
    -> FROM invoice
    -> WHERE company_id = '37687714-2e9d-4daa-aee6-f7d56962f903'
    ->   AND id > '525ae038-0cc3-4f9a-85e6-6f36d43fae40'
    -> ORDER BY id
    -> LIMIT 10;
+----+-------------+---------+------------+-------+--------------------------------+--------------------------------+---------+------+------+----------+-----------------------+
| id | select_type | table   | partitions | type  | possible_keys                  | key                            | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+---------+------------+-------+--------------------------------+--------------------------------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | invoice | NULL       | range | PRIMARY,invoice__company_id_id | invoice__company_id_id,PRIMARY | 76      | NULL |   98 |   100.00 | Using index condition |
+----+-------------+---------+------------+-------+--------------------------------+--------------------------------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)

显示创建表:

CREATE TABLE `invoice` (
  `id` varchar(36) NOT NULL,
  `company_id` varchar(36) NOT NULL DEFAULT '0',
  `invoice_number` varchar(36) NOT NULL DEFAULT '0',
  `identifier` varchar(255) NOT NULL,
  `created_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `created_by` varchar(36) DEFAULT NULL,
  `data_source` varchar(36) NOT NULL,
  `type` varchar(45) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `invoice__company_id_id` (`company_id`,`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1

选择@@optimizer_switch;

use_index_extensions=on

MySQL 版本:

  • 版本:5.7.26-29-57-log
  • innodb_version: 5.7.26-29
  • version_comment:Percona XtraDB Cluster (GPL),Release rel29,Revision 03540a3,WSREP 版本 31.37,wsrep_31.37

显示 'char%' 等变量;

character_set_client    utf8
character_set_connection    utf8
character_set_database  latin1
character_set_filesystem    binary
character_set_results   utf8
character_set_server    latin1
character_set_system    utf8
character_sets_dir  /usr/share/mysql/charsets/

有一些消息来源解释说,company_id 索引本身就足够了:

我一直无法找到有关预期结果的官方文档。这与 id 的数据类型有关吗? mysql+innodb行为的常识有误吗?

【问题讨论】:

  • 此手册页可能是您正在寻找的内容:dev.mysql.com/doc/refman/5.6/en/index-extensions.html 注意这仅适用于 MySQL 5.6 及更高版本。如果您使用的是旧版本的 MySQL,则未实现索引扩展优化。
  • 能否添加“SHOW CREATE TABLE invoice”的输出?
  • @jkavalik 是的,完成了。抱歉,我是想发布那个...
  • mysql版本是多少?
  • @Michael 据我所知,它应该可以工作,但我已经报告了一个错误(尽管是 MariaDB),优化器选择了无意义的 index_merge (就像你的,合并已经存在的列)所以我认为它是相似的optimizer hickup - 您可以尝试禁用 index_merge 或使用ignore index(PRIMARY) 来查看原始索引是否能够像手动扩展索引一样工作。很难说是什么触发了它,可能是那些 UUID 的统计信息,可能是 varchar 本身(有什么理由不使用固定大小的字符?)

标签: mysql indexing query-optimization innodb


【解决方案1】:

我以前遇到过这个问题。这是我对它的分析。

  • 它出现在 MySQL 5.7 和 8.0 中,但显然没有出现在旧版本中,也没有出现在 MariaDB 中。

  • 我更喜欢的“解决方案”是这样更改索引:

       INDEX(company_id)      -- DROP this
       INDEX(company_id, id)  -- ADD this
    

虽然理论上 2 列索引与 InnoDB 的单列索引相同(假设 id 是 PK`),但优化器似乎忽略了这一事实在某些情况下

另外,我喜欢在需要时明确添加 PK。这向模式的未来读者(包括我自己)表明,某些查询受益于附加的 PK。

我还没有找到“索引合并相交”比等效复合索引更快的情况。

我不喜欢使用索引“提示”,因为担心将来数据分布会发生变化,而我的“提示”会使事情变得更糟。

【讨论】:

    【解决方案2】:

    这行不通。

    要使键集分页生效,您需要将自动增量整数作为主 ID/键。现在您正在使用 VARCHAR 并存储 UID。

    您的查询不会选择“下一个”UID“大于”(... AND id > '525ae038-0cc3-4f9a-85e6-6f36d43fae40' ... )。

    当您将主 ID 更改为数字时,这将起作用。 如果您仍然有索引问题,您可以尝试强制 mysql 使用您的索引:

    SELECT * FROM invoice USE INDEX (invoice__company_id_id)
      WHERE company_id = 'someguid' 
        AND id > 12345 
      ORDER BY id
      LIMIT 10
    

    【讨论】:

      猜你喜欢
      • 2018-05-22
      • 2019-05-20
      • 2010-12-07
      • 2010-11-01
      • 2021-12-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-01
      相关资源
      最近更新 更多