如何优化重复 MySQL 排序的选择
How to optimize repeat MySQL sorted selects
假设我有一个包含 a、b 和 c 列的大型数据库。假设我随后希望根据多列上的某种排序 select 第 x 行到第 (x+100) 行。我可以使用 ORDER BY
和 LIMIT
构造来完成此操作:
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
操作,但不幸的是,似乎无法创建包含混合升序和降序的索引。
有什么好的优化方法吗?这似乎是一个相当常见的用例。
这里有一个可能适用于数字列的想法(更像是一种 hack)。
对于要作为排序依据的每一列,添加一个具有值 MAX_TYPE - column_value
的相同类型的新列,其中 MAX_TYPE
是该列的预期最大值。现在向该列添加索引并按它而不是原始列进行排序。
注意:
- 我使用
DECIMAL
而不是 DOUBLE
,因为双精度可能有舍入误差。
- 也许我遗漏了一些东西,因为
MySQL
在使用 ORDER BY
时根本不使用任何索引(即使是单个列)。
- @Rick James 建议的解决方案绝对比使用
MAX_TYPE
更好。
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
| income | expense |
|--------|---------|
| 250.35 | 200.9 |
| 250.35 | 100.35 |
| 300.5 | 210.75 |
我知道这是一个非常不优雅的解决方案,但对于不能牺牲速度的大型数据库,这可能会奏效。
可能唯一的优化是将数字 b
存储为 -b
或者有一个额外的列,其中冗余地包含 -b
。然后
ORDER BY b ASC, c DESC LIMIT...
将被替换为
ORDER BY minusb DESC, c DESC LIMIT...
并且有
INDEX(minusb, c)
只要你确定
- 所有
ORDER BY
项都是同一 table、 中的列名
- 方向相同,
- 和一个
INDEX
存在,它以与 ORDER BY
列表相同的顺序列出所有这些(可选地在 结束 的额外列),
然后优化器可以(但可能选择不)非常有效地使用 INDEX
——包括消耗 LIMIT
.
无论你是全部制作ASC
还是全部制作DESC
都没有关系。 (ASC
可能稍微好一点。)
请记住 LIMIT m, n
必须读取 m+n
行。 (OFFSET
是一个不错的功能,但它没有得到很好的优化。)如果您 "paginating" 通过使用 OFFSET
和 LIMIT
的长列表,最好 "remember where you left off" 以避免扫描 OFFSET
行。 (如果适用,我可以为您提供更多详细信息。)
假设我有一个包含 a、b 和 c 列的大型数据库。假设我随后希望根据多列上的某种排序 select 第 x 行到第 (x+100) 行。我可以使用 ORDER BY
和 LIMIT
构造来完成此操作:
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
操作,但不幸的是,似乎无法创建包含混合升序和降序的索引。
有什么好的优化方法吗?这似乎是一个相当常见的用例。
这里有一个可能适用于数字列的想法(更像是一种 hack)。
对于要作为排序依据的每一列,添加一个具有值 MAX_TYPE - column_value
的相同类型的新列,其中 MAX_TYPE
是该列的预期最大值。现在向该列添加索引并按它而不是原始列进行排序。
注意:
- 我使用
DECIMAL
而不是DOUBLE
,因为双精度可能有舍入误差。 - 也许我遗漏了一些东西,因为
MySQL
在使用ORDER BY
时根本不使用任何索引(即使是单个列)。 - @Rick James 建议的解决方案绝对比使用
MAX_TYPE
更好。
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
| income | expense |
|--------|---------|
| 250.35 | 200.9 |
| 250.35 | 100.35 |
| 300.5 | 210.75 |
我知道这是一个非常不优雅的解决方案,但对于不能牺牲速度的大型数据库,这可能会奏效。
可能唯一的优化是将数字 b
存储为 -b
或者有一个额外的列,其中冗余地包含 -b
。然后
ORDER BY b ASC, c DESC LIMIT...
将被替换为
ORDER BY minusb DESC, c DESC LIMIT...
并且有
INDEX(minusb, c)
只要你确定
- 所有
ORDER BY
项都是同一 table、 中的列名
- 方向相同,
- 和一个
INDEX
存在,它以与ORDER BY
列表相同的顺序列出所有这些(可选地在 结束 的额外列),
然后优化器可以(但可能选择不)非常有效地使用 INDEX
——包括消耗 LIMIT
.
无论你是全部制作ASC
还是全部制作DESC
都没有关系。 (ASC
可能稍微好一点。)
请记住 LIMIT m, n
必须读取 m+n
行。 (OFFSET
是一个不错的功能,但它没有得到很好的优化。)如果您 "paginating" 通过使用 OFFSET
和 LIMIT
的长列表,最好 "remember where you left off" 以避免扫描 OFFSET
行。 (如果适用,我可以为您提供更多详细信息。)