mysql ORDER BY with CASE - 太慢,更快的方法?
mysql ORDER BY with CASE - too slow, faster way?
查看 various answers for ORDER BY with CASE like this one,我发现我在这个遗留应用程序中被迫做的很可能是专家方法;但是,当行数不多时(100,000 或更多的行导致页面加载时间为 10 秒),它就太慢了。
请注意,原始查询旨在解决一个明显常见的问题,即查询分析师需要空排序的日期,而不是通常的排序方式。在这种情况下,datefirstprinted
将降序排列,但所有未打印的记录都应填充到列表顶部。
Original Query 解决了这个问题,但问题的重点是避免派生列 notprintedyet
带来的 filesort
性能损失。
Original Query
SELECT SQL_NO_CACHE
id, daterun, datefirstprinted,
case datefirstprinted when "0000-00-00 00:00:00" then 1 else 0 end as notprintedyet
FROM
patientrecords
WHERE
dateuploaded <> '0000-00-00 00:00:00'
ORDER BY
notprintedyet desc, /* ordered via alias */
datefirstprinted desc
LIMIT 10;
时间1.52s
我发现不按别名 notprintedyet
排序可以节省一点:
Slightly Faster Query
SELECT SQL_NO_CACHE
id, daterun, datefirstprinted,
case datefirstprinted when "0000-00-00 00:00:00" then 1 else 0 end as notprintedyet
FROM
patientrecords
WHERE
dateuploaded <> '0000-00-00 00:00:00'
ORDER BY
datefirstprinted = "0000-00-00 00:00:00" desc, /* directly ordered */
datefirstprinted
LIMIT 10;
时间1.37s
Optimal Speed, but missing required sorting of empty dates first
SELECT SQL_NO_CACHE
id, daterun, datefirstprinted,
case datefirstprinted when "0000-00-00 00:00:00" then 1 else 0 end as notprintedyet
FROM
patientrecords
WHERE
dateuploaded <> '0000-00-00 00:00:00'
ORDER BY
datefirstprinted /* not ordered properly */
LIMIT 10;
时间0.48s
I tried using a view
create view notprinted_patientrecords as (
SELECT id, daterun, datefirstprinted, case datefirstprinted when "0000-00-00 00:00:00" then 1 else 0 end notprintedyet
FROM patientrecords
WHERE dateuploaded <> '0000-00-00 00:00:00'
);
不幸的是当我运行explain
explain select * from notprinted_patientrecords order by notprintedyet desc limit 10;
这表明我仍在使用 filesort
并且需要 1.51s 也就是没有节省全部
Would it be faster if datefirstprinted default is NULL?
也许吧,但在这个旧版应用程序中,这可能比页面加载时间额外增加 5 秒造成的危害更大
What else might we try? Stored procedures? Functions?
更新
As suggested @strawberry - ORDER BY CASE
...
ORDER BY
case datefirstprinted when "0000-00-00 00:00:00" then 1 else 0 end, datefirstprinted
LIMIT 10;
时间1.52s
根据@e4c5 的要求,explain
输出:
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: patientrecords
type: range
possible_keys: dateuploaded,uploads_report
key: dateuploaded
key_len: 5
ref: NULL
rows: 299095
Extra: Using index condition; Using filesort
除了 排序不正确 具有以下方差
rows: 10
Extra: Using where
创建 table 语句
*************************** 1. row ***************************
Table: patientrecords
Create Table: CREATE TABLE `patientrecords` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`datecreated` datetime NOT NULL,
`dateuploaded` datetime NOT NULL,
`daterun` datetime NOT NULL,
`datebilled` datetime NOT NULL,
`datefirstprinted` datetime NOT NULL,
`datelastprinted` datetime NOT NULL,
`client` varchar(5) NOT NULL,
PRIMARY KEY (`id`),
KEY `dateuploaded` (`dateuploaded`),
KEY `daterun` (`daterun`),
KEY `uploads_report` (`dateuploaded`,`client`),
KEY `datefirstprinted` (`datefirstprinted`),
KEY `datelastprinted` (`datelastprinted`)
)
看你的table,首先要注意的是下面的索引是多余的
KEY `dateuploaded` (`dateuploaded`),
它的作用可以由这个来完成
KEY `uploads_report` (`dateuploaded`,`client`),
所以让我们放下 dateuploaded
键。不清楚您是否在任何查询中实际使用了客户列。如果你不这样做,我相信如下更改你的索引会给你一个很大的加速
KEY `uploads_report` (`dateuploaded`,`datefirstprinted`,`client`),
这是因为 mysql 每个 table 只能使用一个索引。由于 dateuploaded 列上的索引正在 where 子句中使用,因此无法使用 datefirstprinted
的索引。但是,如果您将两列合并到同一个索引中,则它可以同时用于排序和位置。
你做了上面的索引之后,这个可能会被去掉:
KEY `datefirstprinted` (`datefirstprinted`),
索引越少,插入和更新的速度就越快。
遵循@e4c5 在连接索引上学到的想法,我尝试在两列上添加一个键(where
中使用的列和 case
中使用的基于 order
子句的列):
alter table
patientrecords
add index
printedvsuploaded (datefirstprinted, dateuploaded);
这最初没有效果,因为 mysql 继续使用索引 dateuploaded
。
但是添加 force index
可以减少查询时间:
SELECT SQL_NO_CACHE
id, daterun, datefirstprinted
FROM
patientrecords
FORCE INDEX (printedvsuploaded)
WHERE
dateuploaded <> '0000-00-00 00:00:00'
ORDER BY
case when datefirstprinted = "0000-00-00 00:00:00" then 1 else 0 end desc,
datefirstprinted
LIMIT 10;
时间0.64秒
值得注意的是,我同意@e4c5 的观点,即额外的索引最终会导致写入性能受到影响;我指望其他路线图开发来帮助减少索引数量。目前,实现这一点会将较大结果集的 10 秒页面加载减少到可管理的 3 秒范围内,然后是将要实现的解决方案。
查看 various answers for ORDER BY with CASE like this one,我发现我在这个遗留应用程序中被迫做的很可能是专家方法;但是,当行数不多时(100,000 或更多的行导致页面加载时间为 10 秒),它就太慢了。
请注意,原始查询旨在解决一个明显常见的问题,即查询分析师需要空排序的日期,而不是通常的排序方式。在这种情况下,datefirstprinted
将降序排列,但所有未打印的记录都应填充到列表顶部。
Original Query 解决了这个问题,但问题的重点是避免派生列 notprintedyet
带来的 filesort
性能损失。
Original Query
SELECT SQL_NO_CACHE
id, daterun, datefirstprinted,
case datefirstprinted when "0000-00-00 00:00:00" then 1 else 0 end as notprintedyet
FROM
patientrecords
WHERE
dateuploaded <> '0000-00-00 00:00:00'
ORDER BY
notprintedyet desc, /* ordered via alias */
datefirstprinted desc
LIMIT 10;
时间1.52s
我发现不按别名 notprintedyet
排序可以节省一点:
Slightly Faster Query
SELECT SQL_NO_CACHE
id, daterun, datefirstprinted,
case datefirstprinted when "0000-00-00 00:00:00" then 1 else 0 end as notprintedyet
FROM
patientrecords
WHERE
dateuploaded <> '0000-00-00 00:00:00'
ORDER BY
datefirstprinted = "0000-00-00 00:00:00" desc, /* directly ordered */
datefirstprinted
LIMIT 10;
时间1.37s
Optimal Speed, but missing required sorting of empty dates first
SELECT SQL_NO_CACHE
id, daterun, datefirstprinted,
case datefirstprinted when "0000-00-00 00:00:00" then 1 else 0 end as notprintedyet
FROM
patientrecords
WHERE
dateuploaded <> '0000-00-00 00:00:00'
ORDER BY
datefirstprinted /* not ordered properly */
LIMIT 10;
时间0.48s
I tried using a view
create view notprinted_patientrecords as (
SELECT id, daterun, datefirstprinted, case datefirstprinted when "0000-00-00 00:00:00" then 1 else 0 end notprintedyet
FROM patientrecords
WHERE dateuploaded <> '0000-00-00 00:00:00'
);
不幸的是当我运行explain
explain select * from notprinted_patientrecords order by notprintedyet desc limit 10;
这表明我仍在使用 filesort
并且需要 1.51s 也就是没有节省全部
Would it be faster if datefirstprinted default is NULL?
也许吧,但在这个旧版应用程序中,这可能比页面加载时间额外增加 5 秒造成的危害更大
What else might we try? Stored procedures? Functions?
更新
As suggested @strawberry - ORDER BY CASE
...
ORDER BY
case datefirstprinted when "0000-00-00 00:00:00" then 1 else 0 end, datefirstprinted
LIMIT 10;
时间1.52s
根据@e4c5 的要求,explain
输出:
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: patientrecords
type: range
possible_keys: dateuploaded,uploads_report
key: dateuploaded
key_len: 5
ref: NULL
rows: 299095
Extra: Using index condition; Using filesort
除了 排序不正确 具有以下方差
rows: 10
Extra: Using where
创建 table 语句
*************************** 1. row ***************************
Table: patientrecords
Create Table: CREATE TABLE `patientrecords` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`datecreated` datetime NOT NULL,
`dateuploaded` datetime NOT NULL,
`daterun` datetime NOT NULL,
`datebilled` datetime NOT NULL,
`datefirstprinted` datetime NOT NULL,
`datelastprinted` datetime NOT NULL,
`client` varchar(5) NOT NULL,
PRIMARY KEY (`id`),
KEY `dateuploaded` (`dateuploaded`),
KEY `daterun` (`daterun`),
KEY `uploads_report` (`dateuploaded`,`client`),
KEY `datefirstprinted` (`datefirstprinted`),
KEY `datelastprinted` (`datelastprinted`)
)
看你的table,首先要注意的是下面的索引是多余的
KEY `dateuploaded` (`dateuploaded`),
它的作用可以由这个来完成
KEY `uploads_report` (`dateuploaded`,`client`),
所以让我们放下 dateuploaded
键。不清楚您是否在任何查询中实际使用了客户列。如果你不这样做,我相信如下更改你的索引会给你一个很大的加速
KEY `uploads_report` (`dateuploaded`,`datefirstprinted`,`client`),
这是因为 mysql 每个 table 只能使用一个索引。由于 dateuploaded 列上的索引正在 where 子句中使用,因此无法使用 datefirstprinted
的索引。但是,如果您将两列合并到同一个索引中,则它可以同时用于排序和位置。
你做了上面的索引之后,这个可能会被去掉:
KEY `datefirstprinted` (`datefirstprinted`),
索引越少,插入和更新的速度就越快。
遵循@e4c5 在连接索引上学到的想法,我尝试在两列上添加一个键(where
中使用的列和 case
中使用的基于 order
子句的列):
alter table
patientrecords
add index
printedvsuploaded (datefirstprinted, dateuploaded);
这最初没有效果,因为 mysql 继续使用索引 dateuploaded
。
但是添加 force index
可以减少查询时间:
SELECT SQL_NO_CACHE
id, daterun, datefirstprinted
FROM
patientrecords
FORCE INDEX (printedvsuploaded)
WHERE
dateuploaded <> '0000-00-00 00:00:00'
ORDER BY
case when datefirstprinted = "0000-00-00 00:00:00" then 1 else 0 end desc,
datefirstprinted
LIMIT 10;
时间0.64秒
值得注意的是,我同意@e4c5 的观点,即额外的索引最终会导致写入性能受到影响;我指望其他路线图开发来帮助减少索引数量。目前,实现这一点会将较大结果集的 10 秒页面加载减少到可管理的 3 秒范围内,然后是将要实现的解决方案。