mysql ORDER BY with CASE - 太慢,更快的方式?

时间:2017-01-17 03:54:33

标签: mysql sql-order-by case filesort

看看various answers for ORDER BY with CASE like this one,我看到我在这个遗留应用程序中被迫做的事情可能是一种专家方法;但是,当行少于平凡时(100,000或更多的行导致10秒的页面加载),它太慢了。

请注意,原始查询旨在解决一个明显常见的问题,即查询分析师需要空的日期与他们通常的排序方式相反。在这种情况下,datefirstprinted将下降,但所有未打印的记录都应填充到列表的顶部。

原始查询解决了这个问题,但问题的关键在于避免派生列filesort附带的notprintedyet性能损失。

  
    

原始查询

  
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进行排序可以节省一点:

  
    

查询速度稍快

  
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.37秒

  
    

最佳速度,但缺少首先排空空日期

  
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.48秒

  

我尝试使用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 ,而且根本没有储蓄

  

如果datefirstprinted default为NULL会更快吗?

也许,但是在这个遗留应用程序中,它可能比页面加载时间的5秒额外伤害

  

我们还能尝试什么?存储过程?功能

更新

  

根据建议@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

创建表语句

*************************** 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`)
)

2 个答案:

答案 0 :(得分:1)

看看你的表,首先要注意的是以下索引是多余的

KEY `dateuploaded` (`dateuploaded`),

它的角色可以通过这个来填补

KEY `uploads_report` (`dateuploaded`,`client`),

所以让我们放下dateuploaded键。目前尚不清楚您是否在任何查询中实际使用客户端列。如果你不这样做,我相信如下改变你的指数会给你一个很大的加速

KEY `uploads_report` (`dateuploaded`,`datefirstprinted`,`client`),

这是因为mysql每个表只能使用一个索引。由于在date子句中使用了dateuploaded列的索引,因此无法使用datefirstprinted的索引。但是,如果将两列组合到同一索引中,则可以在sort和where中使用它。

完成上述索引后,可能会删除此索引:

KEY `datefirstprinted` (`datefirstprinted`),

索引越少,插入和更新速度就越快。

答案 1 :(得分:1)

由于@ 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秒范围,然后是将要实施的解决方案。