【问题标题】:How to optimize repeat MySQL sorted selects如何优化重复 MySQL 排序选择
【发布时间】:2015-07-01 19:11:12
【问题描述】:

假设我有一个包含 a、b 和 c 列的大型数据库。假设我希望根据多列上的某些排序选择第 x 到第 (x+100) 行。我可以使用ORDER BYLIMIT 构造来完成此操作:

SELECT * FROM table_name ORDER BY b ASC, c DESC, a DESC LIMIT x, 100

如果我希望使用相同的顺序(在 b 上升序,在 c 上降序,然后在 a 上降序)但具有不同的范围限制,该怎么办?直观地说,不需要为每个这样的查询重复昂贵的排序操作。

我正在研究使用索引 (http://dev.mysql.com/doc/refman/5.6/en/order-by-optimization.html) 优化 ORDER BY 操作,但不幸的是,似乎无法创建包含混合升序和降序的索引。

有没有好的方法来优化这个?这似乎是一个相当常见的用例。

【问题讨论】:

  • 仅供参考,语法是LIMIT x, 100——它是LIMIT start, count,而不是LIMIT start, end
  • @Barmar 不错。谢谢!
  • b 列是唯一与其他列具有不同方向的列吗?如果是,那么b 列是否是数字?
  • bc 的数据类型是什么?我们能得到它们语义的线索吗? (日期、排名、用户名……)
  • @s3v3n 对于我的实际应用程序,有时我会有不止三列,并且会任意分配升序和降序。

标签: mysql sorting optimization indexing sql-order-by


【解决方案1】:

可能唯一的优化是将数字 b 存储为 -b 或有一个额外的列,其中冗余包含 -b。那么

ORDER BY b ASC, c DESC LIMIT...

将被替换为

ORDER BY minusb DESC, c DESC LIMIT...

还有

INDEX(minusb, c)

只要你确定

  • 所有ORDER BY 项都是同一个表中的列名,
  • 同向,
  • 并且存在一个INDEX,它以与ORDER BY 列表相同的顺序列出所有这些(在end 上可选额外的列),

那么优化器可以(但可以选择不)非常有效地使用INDEX——包括使用LIMIT

无论您是全部制作ASC还是全部制作DESC都没有关系。 (ASC可能会稍微好一点。)

请记住,LIMIT m, n 必须读取 m+n 行。 (OFFSET 是一个不错的功能,但它没有得到很好的优化。)如果您使用 OFFSETLIMIT 在长列表中“分页”,最好“记住您离开的位置”以避免扫描在OFFSET 行上。 (如果适用,我可以为您提供更多详细信息。)

【讨论】:

  • 哦,哇,同样的想法,但使用负值而不是 MAX - x 可能会更好。干得好!
  • 所有DOUBLEs 都是可以否定的,除了 NaN。 INT UNSIGNED 需要像maxint - x 这样的技巧。 INT SIGNED 不太可能有麻烦,除非你碰巧目前有最大的负数。 (例如,TINYINT SIGNED 的范围是 -128 到 +127,因此 -128 不能取反。)
  • DECIMALs 总是可以否定的。
  • 感谢您的建议!在单独的说明中,我确实在分​​页一个长列表。如果您能指出有关避免重新扫描 OFFSET 行的方法的详细信息,我会很高兴。
  • My pagination blog 讨论了为什么以及如何“记住你离开的地方”。
【解决方案2】:

这是一个可能适用于数字列的想法(这相当 hack)。 对于要排序的每一列,添加一个相同类型的新列,其值为MAX_TYPE - column_value,其中MAX_TYPE 是该列的预期最大值。现在向该列添加一个索引并按它而不是原始列进行排序。

注意:

  • 我使用了 DECIMAL 而不是 DOUBLE,因为 double 可能存在舍入错误。
  • 也许我遗漏了一些东西,因为 MySQL 在使用 ORDER BY 时根本不使用任何索引(即使是单列)。
  • @Rick James 建议的解决方案绝对比使用MAX_TYPE 更好。

SQL fiddle:

MySQL 5.6 架构设置

CREATE TABLE `bogus` (
  `income` DECIMAL(7,2),
  `expense` DECIMAL(7,2),
  `expense_inverted` DECIMAL(7,2)
);

ALTER TABLE `bogus` ADD INDEX `income_idx` (`income`);
ALTER TABLE `bogus` ADD INDEX `expense_idx` (`expense`);
ALTER TABLE `bogus` ADD INDEX `expense_inverted_idx` (`expense_inverted`);

INSERT INTO `bogus` (`income`, `expense`)
  VALUES
  (250.35, 200.90),
  (250.35, 100.35),
  (300.50, 210.75);

UPDATE `bogus` SET `expense_inverted` = 99999.99 - `expense`;

查询 1

SELECT income, expense
FROM `bogus`
ORDER BY
  `income` ASC,
  `expense_inverted` ASC; # equivalent of `expense` DESC

Results

| income | expense |
|--------|---------|
| 250.35 |   200.9 |
| 250.35 |  100.35 |
|  300.5 |  210.75 |

我知道这是一个非常不优雅的解决方案,但对于不能牺牲速度的大型数据库 - 这可能会奏效。

【讨论】:

  • 对于单列排序,您当然是对的。可以向前或向后顺序遍历索引。但是,当在多列上使用索引时,我认为情况并非如此。
  • 来自dev.mysql.com/doc/refman/5.6/en/order-by-optimization.html: "在某些情况下,MySQL 无法使用索引来解析ORDER BY...这些情况包括:您将ASCDESC 混合使用例如:@987654337 @"
  • 好吧,你说得对,我不知道这个。我正在删除我的答案。
  • 这是一个 hack,但在某些情况下它是个好主意。谢谢!
猜你喜欢
  • 2016-06-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-02-08
相关资源
最近更新 更多