MySql 性能調優之case when then

1 解釋:

SELECT            
    case                   -------------若是
    when sex='1' then '男' -------------sex='1',則返回值'男'
    when sex='2' then '女' -------------sex='2',則返回值'女'  
    else 0              -------------其餘的返回'其餘’                                                                             
    end                 -------------結束                                                                                                 
   from   sys_user    --------總體理解: 在sys_user表中若是sex='1',則返回值'男'若是sex='2',則返回值'女' 不然返回'其餘’

---用法一:java

SELECT 
            CASE WHEN STATE = '1' THEN '成功' 
                 WHEN STATE = '2' THEN '失敗'
            ELSE '其餘' END  
            FROM  SYS_SCHEDULER

---用法二:mysql

SELECT STATE
            CASE WHEN '1' THEN '成功' 
                 WHEN '2' THEN '失敗'
            ELSE '其餘' END  
            FROM  SYS_SCHEDULER

2 場景

最近在作項目,涉及到開發統計報表相關的任務,因爲數據量相對較多,以前寫的查詢語句查詢五十萬條數據大概須要十秒左右的樣子,後來利用sum,case...when...重寫SQL性能一會兒提升到一秒鐘就解決了。這裏爲了簡潔明瞭的闡述問題和解決的方法,我簡化一下需求模型。 輸入圖片說明 在日常開發中,咱們經常會遇到這樣的一些統計的需求,經過統計有效數據來指導公司的運營,並計劃將來的發展。 好比咱們有一張訂單表,當payment_method = 1 的時候 爲支付寶,type=2的時候爲微信支付。spring

CREATE TABLE  mall_order (
  `id` varchar(32) NOT NULL COMMENT '商品訂單號',
  `title` varchar(100) DEFAULT NULL COMMENT '商品訂單標題',
  `total_fee` bigint(20) DEFAULT '0' COMMENT '總金額',
  `create_time` datetime DEFAULT NULL COMMENT '建立商品訂單的完整時間',
  `type` int(11) DEFAULT '1' COMMENT '類型',
  `shop_id` bigint(20) NOT NULL COMMENT '商品訂單所屬商戶',
  `shop_name` varchar(100) DEFAULT NULL COMMENT '商戶名稱',
  `shop_serial` varchar(32) DEFAULT NULL COMMENT '商戶編號',
  `status` int(11) NOT NULL DEFAULT '0' COMMENT '訂單狀態',
  `source` int(11) NOT NULL DEFAULT '0' COMMENT '訂單來源',
  `payment_method` int(11) NOT NULL DEFAULT '1' COMMENT '訂單支付方式',
  `receive_goods_method` int(11) NOT NULL DEFAULT '0' COMMENT '收貨方式',
  `deliver_fee` bigint(20) NOT NULL DEFAULT '0' COMMENT '快遞費',
  `update_time` datetime NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT '更新時間',
  `pay_deadline` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '發起支付的截止時間',
  `locked` tinyint(4) NOT NULL DEFAULT '0' COMMENT '優惠鎖定標識,1:已鎖定;0:未鎖定',
  PRIMARY KEY (`id`),
  KEY `idx_shop_time` (`shop_id`,`create_time`) USING BTREE,
  KEY `idx_create_time` (`create_time`) USING BTREE,
  KEY `idx_shop_branch_time` (`shop_branch_id`,`create_time`) USING BTREE,
  KEY `idx_shop_customer` (`shop_id`,`customer_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品訂單'

若是咱們寫一個這樣的統計sql:sql

SELECT 
  (SELECT SUM(total_fee) FROM mall_order SS WHERE SS.create_time = S.create_time AND SS.payment_method = 1) AS 'zhifubaoTotalOrderAmount',
  (SELECT COUNT(*) FROM mall_order SS WHERE SS.create_time = S.create_time AND SS.payment_method = 1) AS 'zhifubaoTotalOrderNum',
  (SELECT SUM(total_fee) FROM mall_order SS WHERE SS.create_time = S.create_time AND SS.payment_method = 2) AS 'weixinTotalOrderAmount',
  (SELECT COUNT(*) FROM mall_order SS WHERE SS.create_time = S.create_time AND SS.payment_method = 2) AS 'weixinTotalOrderNum'
 FROM mall_order S WHERE S.create_time > '2016-05-01' AND S.create_time < '2016-08-01' 
 GROUP BY S.create_time ORDER BY S.create_time ASC;

這種寫法採用了子查詢的方式,在沒有加索引的狀況下,55萬條數據執行這句SQL,在workbench下等待了將近十分鐘,最後報了一個鏈接中斷,經過explain解釋器能夠看到SQL的執行計劃以下:spring-mvc

EXPLAIN SELECT 
  (SELECT SUM(total_fee) FROM product_order SS WHERE SS.create_time = S.create_time AND SS.payment_method = 1) AS 'zhifubaoTotalOrderAmount',
  (SELECT COUNT(*) FROM product_order SS WHERE SS.create_time = S.create_time AND SS.payment_method = 1) AS 'zhifubaoTotalOrderNum',
  (SELECT SUM(total_fee) FROM product_order SS WHERE SS.create_time = S.create_time AND SS.payment_method = 2) AS 'weixinTotalOrderAmount',
  (SELECT COUNT(*) FROM product_order SS WHERE SS.create_time = S.create_time AND SS.payment_method = 2) AS 'weixinTotalOrderNum'
 FROM product_order S 
 GROUP BY S.create_time ORDER BY S.create_time ASC;

此圖爲加了create_time 索引後的查詢,查看執行計劃發現掃描的行數減小了不少,再也不進行全表掃描了:微信

輸入圖片說明 每個查詢都進行了全表掃描,4個子查詢DEPENDENT SUBQUERY說明依賴於外部查詢,這種查詢機制是先進行外部查詢,查詢出group by後的日期結果,而後子查詢分別查詢對應的日期中payment_method = 1,payment_method = 2等的數量,其效率可想而知。mvc

若是當數據量達到百萬級別的話,查詢速度確定是不能容忍的。一直在想有沒有一種辦法,可否直接遍歷一次就查詢出全部的結果,相似於遍歷java中的list集合,遇到某個條件就計數一次,這樣進行一次全表掃描就能夠查詢出結果集,結果索引,效率應該會很高, 同時在統計的時候避免屢次查詢。函數

爲此,咱們能夠利用利用sum聚合函數,加上case...when...then...這種「陌生」的用法,有效的解決了這個問題。性能

select S.create_time,
   sum(case when S.payment_method =1 then 1 else 0 end) as 'zhifubaoOrderNum',
   sum(case when S.payment_method =1 then total_fee else 0 end) as 'zhifubaoOrderAmount',
   sum(case when S.payment_method =2 then 1 else 0 end) as 'weixinOrderNum',
   sum(case when S.payment_method =2 then total_fee else 0 end) as 'weixinOrderAmount'
 from mall_order S where S.create_time > '2015-05-01' and S.create_time < '2016-08-01' 
 GROUP BY S.create_time order by S.create_time asc;

輸入圖片說明

經過執行計劃發現,雖然掃描的行數變多了,可是隻進行了一次全表掃描,並且是SIMPLE簡單查詢,因此執行效率天然就高了。微信支付

天: DATE_FORMAT(po.create_time,'%Y-%m-%d') AS statDate 
周: DATE_FORMAT(DATE_SUB(po.create_time,INTERVAL WEEKDAY(po.create_time) DAY),'%Y-%m-%d') AS statDate
月:DATE_FORMAT(po.create_time,'%Y-%m') AS statDate

mysql 函數:

select curdate();                       -- 獲取當前日期
select DATE_ADD(curdate(),interval -day(curdate())+1 day)   -- 獲取本月第一天
select last_day(curdate());   -- 獲取當月最後一天
select date_add(curdate()-day(curdate())+1,interval 1 month ) -- 獲取下個月的第一天
select DATEDIFF(date_add(curdate()-day(curdate())+1,interval 1 month ),DATE_ADD(curdate(),interval -day(curdate())+1 day)) from dual -- 獲取當前月的天數

注:spring-mvc 中,SQlMap 再是用獲取本月第一天時引用如下語句 :

SELECT DATE_ADD(CURDATE(),INTERVAL 1-DAYOFMONTH(CURDATE()) DAY); -- 獲取本月第一天

3 本期薦書:

豆瓣:https://book.douban.com/subject/25801248/

輸入圖片說明

相關文章
相關標籤/搜索