是否有可能使MySQL使用ORDER的索引由1 DESC,2 ASC?

You*_*nse 19 mysql indexing materialized-path-pattern

我有一个物化的路径驱动的公告板.它使用以下查询来按顺序获取消息,

SELECT * FROM Board ORDER by root DESC, path ASC LIMIT 0,100
Run Code Online (Sandbox Code Playgroud)

where rootid线程的根消息,path是物化路径.

但是,我使用索引进行此查询的努力都没有取得任何成功.

mysql> explain extended select path from Board order by root desc, path asc limit 100;
+-------+---------------+----------+---------+------+-------+----------+----------------------------+
| type  | possible_keys | key      | key_len | ref  | rows  | filtered | Extra
+-------+---------------+----------+---------+------+-------+----------+-----------------------------
| index | NULL          | rootpath | 261     | NULL | 21998 |   100.00 | Using index; Using filesort
Run Code Online (Sandbox Code Playgroud)

目前它显示列下表中所有行的数量rows.我想知道,有没有办法减少这个数字或以任何其他方式优化查询?

CREATE TABLE `Board` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `path` varchar(255) NOT NULL DEFAULT '0',
  `root` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `root` (`root`),
  KEY `path` (`path`),
  KEY `rootpath` (`root`,`path`)
)
Run Code Online (Sandbox Code Playgroud)

查询的主要问题是分页 - 我需要从上一页上最​​后一页旁边的消息开始第二页.这就是为什么我想要它直接的方式 - 没有suplelects和东西.
但是当前的设置并不是很好,因为它从线程的中间开始第二页,但它至少是合乎逻辑的.

fra*_*ail 21

本文将很好地解释您遇到的问题.而重要的是:

最典型的情况是,当您想要按不同方向的两个列进行排序时:...按价格排序ASC,日期DESC限制10如果您按升序编号(价格,日期),则无法很好地优化此查询 - 将需要外部排序("filesort").如果您能够在价格ASC上构建索引,则日期DESC相同的查询可以按照有序排序顺序检索数据.

该文章还提到了该问题的有效解决方法:使第二个"order"子句反转:

但是,您可以通过使用"reverse_date"列并将其用于排序来解决此问题.使用MySQL 5.0,您甚至可以使用触发器将其更新为实际日期更新,因此它变得不那么难看.实际上,这就是为什么你会在Wikipedia表结构中看到"reverse_timestamp"字段的原因.

也来自官方MySQL文档:

在某些情况下,MySQL不能使用索引来解析ORDER BY,尽管它仍然使用索引来查找与WHERE子句匹配的行.这些情况包括以下内容:

.....

混合ASC和DESC:

SELECT*FROM t1 ORDER BY key_part1 DESC,key_part2 ASC;

作为一个建议,你最好有一个AND reversed_root是一个列Integer.MAX_VALUE - root的索引(reversed_root,path).然后你可以有一个查询:

SELECT * FROM Board ORDER by reversed_root ASC,path ASC LIMIT 0,100
Run Code Online (Sandbox Code Playgroud)


Rol*_*DBA 15

你的原始查询

SELECT * FROM Board ORDER by root DESC, path ASC LIMIT 0,100;
Run Code Online (Sandbox Code Playgroud)

创建一个表来保存root的负值,称为BoardDisplayOrder,在其中添加名为rootinv的新列.

首先是示例数据和您的原始查询:

mysql> drop database if exists YourCommonSense;
Query OK, 2 rows affected (0.06 sec)

mysql> create database YourCommonSense;
Query OK, 1 row affected (0.00 sec)

mysql> use YourCommonSense
Database changed
mysql> CREATE TABLE `Board` (
    ->   `id` int(11) NOT NULL AUTO_INCREMENT,
    ->   `path` varchar(255) NOT NULL DEFAULT '0',
    ->   `root` int(11) NOT NULL DEFAULT '0',
    ->   PRIMARY KEY (`id`),
    ->   KEY `root` (`root`),
    ->   KEY `path` (`path`),
    ->   KEY `rootpath` (`root`,`path`)
    -> );
Query OK, 0 rows affected (0.11 sec)

mysql> INSERT INTO Board (path,root) VALUES
    -> ('Rolando Edwards',30),
    -> ('Daniel Edwards',30),
    -> ('Pamela Edwards',30),
    -> ('Dominiuqe Edwards',40),
    -> ('Diamond Edwards',40),
    -> ('Richard Washington',50),
    -> ('George Washington',50),
    -> ('Synora Washington',50);
Query OK, 8 rows affected (0.05 sec)
Records: 8  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM Board;
+----+--------------------+------+
| id | path               | root |
+----+--------------------+------+
|  2 | Daniel Edwards     |   30 |
|  3 | Pamela Edwards     |   30 |
|  1 | Rolando Edwards    |   30 |
|  5 | Diamond Edwards    |   40 |
|  4 | Dominiuqe Edwards  |   40 |
|  7 | George Washington  |   50 |
|  6 | Richard Washington |   50 |
|  8 | Synora Washington  |   50 |
+----+--------------------+------+
8 rows in set (0.00 sec)

mysql> SELECT * FROM Board ORDER by root DESC, path ASC LIMIT 0,100;
+----+--------------------+------+
| id | path               | root |
+----+--------------------+------+
|  7 | George Washington  |   50 |
|  6 | Richard Washington |   50 |
|  8 | Synora Washington  |   50 |
|  5 | Diamond Edwards    |   40 |
|  4 | Dominiuqe Edwards  |   40 |
|  2 | Daniel Edwards     |   30 |
|  3 | Pamela Edwards     |   30 |
|  1 | Rolando Edwards    |   30 |
+----+--------------------+------+
8 rows in set (0.00 sec)

mysql> EXPLAIN SELECT * FROM Board ORDER by root DESC, path ASC LIMIT 0,100;
+----+-------------+-------+-------+---------------+----------+---------+------+------+-----------------------------+
| id | select_type | table | type  | possible_keys | key      | key_len | ref  | rows | Extra                       |
+----+-------------+-------+-------+---------------+----------+---------+------+------+-----------------------------+
|  1 | SIMPLE      | Board | index | NULL          | rootpath | 261     | NULL |    8 | Using index; Using filesort |
+----+-------------+-------+-------+---------------+----------+---------+------+------+-----------------------------+
1 row in set (0.00 sec)

mysql>
Run Code Online (Sandbox Code Playgroud)

接下来,使用rootinv和涉及rootinv的索引创建表BoardDisplayOrder:

mysql> CREATE TABLE BoardDisplayOrder LIKE Board;
Query OK, 0 rows affected (0.09 sec)

mysql> ALTER TABLE BoardDisplayOrder DROP INDEX root;
Query OK, 0 rows affected (0.11 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> ALTER TABLE BoardDisplayOrder DROP INDEX path;
Query OK, 0 rows affected (0.09 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> ALTER TABLE BoardDisplayOrder DROP INDEX rootpath;
Query OK, 0 rows affected (0.08 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> ALTER TABLE BoardDisplayOrder ADD COLUMN rootinv int(11) NOT NULL;
Query OK, 0 rows affected (0.17 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> ALTER TABLE BoardDisplayOrder ADD INDEX rootpathid (rootinv,path,id,root);
Query OK, 0 rows affected (0.11 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> SHOW CREATE TABLE BoardDisplayOrder \G
*************************** 1. row ***************************
       Table: BoardDisplayOrder
Create Table: CREATE TABLE `boarddisplayorder` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `path` varchar(255) NOT NULL DEFAULT '0',
  `root` int(11) NOT NULL DEFAULT '0',
  `rootinv` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `rootpathid` (`rootinv`,`path`,`id`,`root`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

mysql>
Run Code Online (Sandbox Code Playgroud)

然后,填充BoardDisplayOrder:

mysql> INSERT INTO BoardDisplayOrder (id,path,root,rootinv)
    -> SELECT id,path,root,-root FROM Board;
Query OK, 8 rows affected (0.06 sec)
Records: 8  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM BoardDisplayOrder;
+----+--------------------+------+---------+
| id | path               | root | rootinv |
+----+--------------------+------+---------+
|  7 | George Washington  |   50 |     -50 |
|  6 | Richard Washington |   50 |     -50 |
|  8 | Synora Washington  |   50 |     -50 |
|  5 | Diamond Edwards    |   40 |     -40 |
|  4 | Dominiuqe Edwards  |   40 |     -40 |
|  2 | Daniel Edwards     |   30 |     -30 |
|  3 | Pamela Edwards     |   30 |     -30 |
|  1 | Rolando Edwards    |   30 |     -30 |
+----+--------------------+------+---------+
8 rows in set (0.00 sec)

mysql>
Run Code Online (Sandbox Code Playgroud)

现在,对BoardDisplayOrder运行查询但在rootinv上没有DESC:

mysql> SELECT id,path,root FROM BoardDisplayOrder ORDER by rootinv, path LIMIT 0,100;
+----+--------------------+------+
| id | path               | root |
+----+--------------------+------+
|  7 | George Washington  |   50 |
|  6 | Richard Washington |   50 |
|  8 | Synora Washington  |   50 |
|  5 | Diamond Edwards    |   40 |
|  4 | Dominiuqe Edwards  |   40 |
|  2 | Daniel Edwards     |   30 |
|  3 | Pamela Edwards     |   30 |
|  1 | Rolando Edwards    |   30 |
+----+--------------------+------+
8 rows in set (0.00 sec)

mysql> EXPLAIN SELECT id,path,root FROM BoardDisplayOrder ORDER by rootinv, path LIMIT 0,100;
+----+-------------+-------------------+-------+---------------+------------+---------+------+------+-------------+
| id | select_type | table             | type  | possible_keys | key        | key_len | ref  | rows | Extra       |
+----+-------------+-------------------+-------+---------------+------------+---------+------+------+-------------+
|  1 | SIMPLE      | BoardDisplayOrder | index | NULL          | rootpathid | 269     | NULL |    8 | Using index |
+----+-------------+-------------------+-------+---------------+------------+---------+------+------+-------------+
1 row in set (0.00 sec)

mysql>
Run Code Online (Sandbox Code Playgroud)

试试看!!!

警告

这很容易做到,因为root是INT.

如果root是VARCHAR,则rootinv必须是翻转字符.换一种说法,

  • A - > Z
  • B - > Y
  • ...
  • M - > N
  • N - > M
  • ...
  • Y - > B
  • Z - > A

这主要适用于执行DESC所需的任何字段.问题源于MySQL没有在索引内部对键作为ASC或DESC进行排序.索引中的所有内容都是升序的.这就是为什么当您看到处理程序统计信息时SHOW GLOBAL STATUS LIKE 'handler%';,您会看到以下内容:

等等.

根据当前的MySQL文档

index_col_name规范可以以ASC或DESC结尾.这些关键字允许用于将来的扩展,以指定升序或降序索引值存储.目前,他们被解析但被忽略; 索引值始终按升序存储.

试试看!!!

更新2012-05-04 06:54美国东部时间

@ frail对我的回答发表评论

ALTER TABLE BoardDisplayOrder ADD INDEX rootpathid(rootinv,path,id,root)对我来说似乎没必要,ALTER TABLE BoardDisplayOrder ADD INDEX rootpathid(rootinv,path)应该足够了

我的解决方案的原因ALTER TABLE BoardDisplayOrder ADD INDEX rootpathid (rootinv,path,id,root)是提供覆盖索引.此实例中的覆盖索引将:

  • 始终有所需的列进行检索
  • 将提高解释计划的质量,因为
    • 查询永远不会从表中读取数据检索
    • 查询只会从索引中读取数据检索
    • 导致索引范围扫描

想想原始查询,

SELECT * FROM Board ORDER by root DESC, path ASC LIMIT 0,100;
Run Code Online (Sandbox Code Playgroud)

这需要检索三列path,id和root.因此,他们需要在索引中.当然,指数增加的规模将是权衡.如果Board表非常大,如果可以更快地进行检索,有些人不会担心空间.如果根路径索引只是(rootinv,path),则每个索引范围扫描都会伴随对表中的其余列的ref查找.这就是我选择的原因ALTER TABLE BoardDisplayOrder ADD INDEX rootpathid (rootinv,path,id,root);


Kev*_*ell 5

在这种情况下,数据本身不适合以您需要的方式进行检索,可能适合创建一个包含您需要的信息的附加列 - 这将允许您按照所需的顺序进行检索.

在这种情况下尤其适合,因为看起来数据本身一旦保存就不会更新.邮件发布后,它们不会更新(或者从我最初的阅读中看起来如此).

假设你走这条路,我建议的步骤是:

  • root_path向表中添加新列.
  • 执行此更新语句update Board set root_path = root + path.(您可能必须根据现有列的数据类型进行调整.)
  • 每当向表中添加新行时,也要添加此新列.(这可以通过触发器来处理,但我会对触发器保持警惕,因为当人们更改代码的其他部分时,它们可能会被忽略.)

然后,您应该能够在该新列上设置索引并针对该列编写选择 - 根据需要命中索引.

我相信即使其中一个键必须以相反的顺序排序,这仍然有效.

CREATE TABLE foo
(
  id serial NOT NULL,
  int_field integer DEFAULT 0,
  varchar_field character varying(255),
  composite_field character varying(255),
  CONSTRAINT foo_pkey PRIMARY KEY (id )
);

CREATE INDEX composite_field_idx ON foo (composite_field);

INSERT INTO foo (int_field, varchar_field, composite_field) VALUES 
(1,'t','t1'),
(2,'z','z2'),
(2,'w','w2'),
(4,'u','u4'),
(5,'u','u5'),
(5,'x','x5'),
(7,'v','v7');

explain select * from foo order by composite_field desc;
Run Code Online (Sandbox Code Playgroud)

运行上面的代码,explain语句应该显示被引用的关键composite_field_idx.

查询的结果是:

select * from foo order by composite_field desc;

 id | int_field | varchar_field | composite_field 
----+-----------+---------------+-----------------
  2 |         2 | z             | z2
  6 |         5 | x             | x5
  3 |         2 | w             | w2
  7 |         7 | v             | v7
  5 |         5 | u             | u5
  4 |         4 | u             | u4
  1 |         1 | t             | t1
Run Code Online (Sandbox Code Playgroud)