在mysql中为键集分页编制索引

时间:2019-07-26 16:25:03

标签: mysql indexing query-optimization innodb

我正在尝试在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` (`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集群(GPL),版本rel29,修订版03540a3,WSREP版本31.37,wsrep_31.37

有一些资料说明,单靠company_id索引就足以满足此要求:

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

3 个答案:

答案 0 :(得分:1)

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

  • 它发生在MySQL 5.7和8.0中,但显然不在较早版本中,在MariaDB中也没有。

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

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

尽管从理论上讲2列索引与InnoDB的1列索引相同(假设id是PK`),但在某些情况下,优化程序似乎忽略了这个事实

此外,我想在需要时明确添加PK 。这向未来的模式读者(包括我自己)发出信号,表明附加的PK可以使某些查询受益。

我还没有发现“索引合并相交”比同等的复合索引快的情况。

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

答案 1 :(得分:0)

一个猜测...

ENGINE=InnoDB DEFAULT CHARSET=latin1

character_set_client    utf8
character_set_connection    utf8
character_set_results   utf8

我希望它将转换字符集而不必大惊小怪

 WHERE company_id = '37687714-2e9d-4daa-aee6-f7d56962f903'
   AND id > '525ae038-0cc3-4f9a-85e6-6f36d43fae40'

请提供此内容;也许会提供一个线索:

EXPLAIN FORMAT=JSON SELECT ...

答案 2 :(得分:0)

这行不通。

要使键集分页生效,您需要使用自动增量整数作为主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