一頓騷操作!我將 SQL 耗時從 30248.271s 優化到 0.001s

2020-12-25 網易

  

  場景

  我用的資料庫是mysql5.6,下面簡單的介紹下場景

  課程表

  create table Course(

  c_id int PRIMARY KEY,

  name varchar(10)

  )

  數據100條

  學生表:

  create table Student(

  id int PRIMARY KEY,

  name varchar(10)

  )

  數據70000條

  學生成績表SC

  CREATE table SC(

  sc_id int PRIMARY KEY,

  s_id int,

  c_id int,

  score int

  )

  數據70w條

  查詢目的:

  查找語文考100分的考生

  查詢語句:

  select s.* from Student s where s.s_id in (select s_id from SC sc where sc.c_id = 0 and sc.score = 100 )

  執行時間:30248.271s

  暈,為什麼這麼慢,先來查看下查詢計劃:

  EXPLAIN

  select s.* from Student s where s.s_id in (select s_id from SC sc where sc.c_id = 0 and sc.score = 100 )
image

  發現沒有用到索引,type全是ALL,那麼首先想到的就是建立一個索引,建立索引的欄位當然是在where條件的欄位。

  先給sc表的c_id和score建個索引

  CREATE index sc_c_id_index on SC(c_id);
CREATE index sc_score_index on SC(score);

  再次執行上述查詢語句,時間為: 1.054s

  快了3w多倍,大大縮短了查詢時間,看來索引能極大程度的提高查詢效率,建索引很有必要,很多時候都忘記建

  索引了,數據量小的的時候壓根沒感覺,這優化的感覺挺爽。

  但是1s的時間還是太長了,還能進行優化嗎,仔細看執行計劃:

  image

  查看優化後的sql:

  SELECT
`YSB`.`s`.`s_id` AS `s_id`,
`YSB`.`s`.`name` AS `name`
FROM
`YSB`.`Student` `s`
WHERE
< in_optimizer > (
`YSB`.`s`.`s_id` ,< EXISTS > (
SELECT
FROM
`YSB`.`SC` `sc`
WHERE
(
(`YSB`.`sc`.`c_id` = 0)
AND (`YSB`.`sc`.`score` = 100)
AND (
< CACHE > (`YSB`.`s`.`s_id`) = `YSB`.`sc`.`s_id`
)
)
)
)

  補充:這裡有網友問怎麼查看優化後的語句

  方法如下:

  在命令窗口執行

  
image

  有type=all

  按照我之前的想法,該sql的執行的順序應該是先執行子查詢

  select s_id from SC sc where sc.c_id = 0 and sc.score = 100

  耗時:0.001s

  得到如下結果:

  image

  然後再執行

  select s.* from Student s where s.s_id in(7,29,5000)

  耗時:0.001s

  這樣就是相當快了啊,Mysql竟然不是先執行裡層的查詢,而是將sql優化成了exists子句,並出現了EPENDENT SUBQUERY,

  mysql是先執行外層查詢,再執行裡層的查詢,這樣就要循環70007*8次。

  那麼改用連接查詢呢?

  SELECT s.* from

  Student s

  INNER JOIN SC sc

  on sc.s_id = s.s_id

  where sc.c_id=0 and sc.score=100

  這裡為了重新分析連接查詢的情況,先暫時刪除索引sc_c_id_index,sc_score_index

  執行時間是:0.057s

  效率有所提高,看看執行計劃:

  
image

  這裡有連表的情況出現,我猜想是不是要給sc表的s_id建立個索引

  CREATE index sc_s_id_index on SC(s_id);

  show index from SC

  
image

  在執行連接查詢

  時間: 1.076s,竟然時間還變長了,什麼原因?查看執行計劃:

  
image

  優化後的查詢語句為:

  SELECT
`YSB`.`s`.`s_id` AS `s_id`,
`YSB`.`s`.`name` AS `name`
FROM
`YSB`.`Student` `s`
JOIN `YSB`.`SC` `sc`
WHERE
(
(
`YSB`.`sc`.`s_id` = `YSB`.`s`.`s_id`
)
AND (`YSB`.`sc`.`score` = 100)
AND (`YSB`.`sc`.`c_id` = 0)
)

  貌似是先做的連接查詢,再進行的where條件過濾

  回到前面的執行計劃:

  
image

  這裡是先做的where條件過濾,再做連表,執行計劃還不是固定的,那麼我們先看下標準的sql執行順序:

  
image

  正常情況下是先join再進行where過濾,但是我們這裡的情況,如果先join,將會有70w條數據發送join做操,因此先執行where

  過濾是明智方案,現在為了排除mysql的查詢優化,我自己寫一條優化後的sql

  SELECT
s.*
FROM
(
SELECT
*
FROM
SC sc
WHERE
sc.c_id = 0
AND sc.score = 100
) t
INNER JOIN Student s ON t.s_id = s.s_id

  即先執行sc表的過濾,再進行表連接,執行時間為:0.054s

  和之前沒有建s_id索引的時間差不多

  查看執行計劃:

  
image

  先提取sc再連表,這樣效率就高多了,現在的問題是提取sc的時候出現了掃描表,那麼現在可以明確需要建立相關索引

  CREATE index sc_c_id_index on SC(c_id);
CREATE index sc_score_index on SC(score);

  再執行查詢:

  SELECT
s.*
FROM
(
SELECT
*
FROM
SC sc
WHERE
sc.c_id = 0
AND sc.score = 100
) t
INNER JOIN Student s ON t.s_id = s.s_id

  執行時間為:0.001s,這個時間相當靠譜,快了50倍

  執行計劃:

  
image

  我們會看到,先提取sc,再連表,都用到了索引。

  那麼再來執行下sql

  SELECT s.* from

  Student s

  INNER JOIN SC sc

  on sc.s_id = s.s_id

  where sc.c_id=0 and sc.score=100

  執行時間0.001s

  執行計劃:

  image

  這裡是mysql進行了查詢語句優化,先執行了where過濾,再執行連接操作,且都用到了索引。

  2015-04-30日補充:最近又重新導入一些生產數據,經測試發現,前幾天優化完的sql執行效率又變低了

  調整內容為SC表的數據增長到300W,學生分數更為離散。

  先回顧下:

  show index from SC

  
image

  執行sql

  SELECT s.* from

  Student s

  INNER JOIN SC sc

  on sc.s_id = s.s_id

  where sc.c_id=81 and sc.score=84

  執行時間:0.061s,這個時間稍微慢了點

  執行計劃:

  image

  這裡用到了intersect併集操作,即兩個索引同時檢索的結果再求併集,再看欄位score和c_id的區分度,

  單從一個欄位看,區分度都不是很大,從SC表檢索,c_id=81檢索的結果是70001,score=84的結果是39425

  而c_id=81 and score=84 的結果是897,即這兩個欄位聯合起來的區分度是比較高的,因此建立聯合索引查詢效率

  將會更高,從另外一個角度看,該表的數據是300w,以後會更多,就索引存儲而言,都是不小的數目,隨著數據量的

  增加,索引就不能全部加載到內存,而是要從磁碟去讀取,這樣索引的個數越多,讀磁碟的開銷就越大,因此根據具體

  業務情況建立多列的聯合索引是必要的,那麼我們來試試吧。

  alter table SC drop index sc_c_id_index;
alter table SC drop index sc_score_index;
create index sc_c_id_score_index on SC(c_id,score);

  執行上述查詢語句,消耗時間為:0.007s,這個速度還是可以接收的

  執行計劃:

  
image

  該語句的優化暫時告一段落

  總結:

  1.mysql嵌套子查詢效率確實比較低

  2.可以將其優化成連接查詢

  3.連接表時,可以先用where條件對表進行過濾,然後做表連接

  (雖然mysql會對連表語句做優化)

  4.建立合適的索引,必要時建立多列聯合索引

  5.學會分析sql執行計劃,mysql會對sql進行優化,所以分析執行計劃很重要

  索引優化

  上面講到子查詢的優化,以及如何建立索引,而且在多個欄位索引時,分別對欄位建立了單個索引

  後面發現其實建立聯合索引效率會更高,尤其是在數據量較大,單個列區分度不高的情況下。

  單列索引

  查詢語句如下:

  select * from user_test_copy where sex = 2 and type = 2 and age = 10

  索引:

  CREATE index user_test_index_sex on user_test_copy(sex);
CREATE index user_test_index_type on user_test_copy(type);
CREATE index user_test_index_age on user_test_copy(age);

  分別對sex,type,age欄位做了索引,數據量為300w,查詢時間:0.415s

  執行計劃:

  
image_thumb3

  發現type=index_merge

  這是mysql對多個單列索引的優化,對結果集採用intersect併集操作

  多列索引

  我們可以在這3個列上建立多列索引,將表copy一份以便做測試

  create index user_test_index_sex_type_age on user_test(sex,type,age);

  查詢語句:

  select * from user_test where sex = 2 and type = 2 and age = 10

  執行時間:0.032s,快了10多倍,且多列索引的區分度越高,提高的速度也越多

  執行計劃:

  image_thumb5

  最左前綴

  多列索引還有最左前綴的特性:

  執行一下語句:

  select * from user_test where sex = 2
select * from user_test where sex = 2 and type = 2
select * from user_test where sex = 2 and age = 10

  都會使用到索引,即索引的第一個欄位sex要出現在where條件中

  索引覆蓋

  就是查詢的列都建立了索引,這樣在獲取結果集的時候不用再去磁碟獲取其它列的數據,直接返回索引數據即可

  如:

  select sex,type,age from user_test where sex = 2 and type = 2 and age = 10

  執行時間:0.003s

  要比取所有欄位快的多

  排序select * from user_test where sex = 2 and type = 2 ORDER BY user_name

  時間:0.139s

  在排序欄位上建立索引會提高排序的效率

  create index user_name_index on user_test(user_name)

  最後附上一些sql調優的總結,以後有時間再深入研究

  


  1.   列類型儘量定義成數值類型,且長度儘可能短,如主鍵和外鍵,類型欄位等等

      


  2.   建立單列索引

      


  3.   根據需要建立多列聯合索引

      

  當單個列過濾之後還有很多數據,那麼索引的效率將會比較低,即列的區分度較低,

  那麼如果在多個列上建立索引,那麼多個列的區分度就大多了,將會有顯著的效率提高。

  

  1.   根據業務場景建立覆蓋索引

      

  只查詢業務需要的欄位,如果這些欄位被索引覆蓋,將極大的提高查詢效率

  

  1.   多表連接的欄位上需要建立索引

      

  這樣可以極大的提高表連接的效率

  


  1.   where條件欄位上需要建立索引

      


  2.   排序欄位上需要建立索引

      


  3.   分組欄位上需要建立索引

      


  4.   Where條件上不要使用運算函數,以免索引失效

      

  福

相關焦點

  • 一波騷操作,我把 SQL 執行效率提高了 10,000,000 倍!
    = 0 and sc.score = 100 )執行時間:30248.271s為什麼這麼慢?= 100耗時:0.001s得到如下結果:然後再執行select s.* from Student s where s.s_id in(7,29,5000)耗時:0.001s這樣就是相當快了啊
  • SparkSQL操作insert overwrite table到hive慢
    在使用spark寫hive過程中,發現最耗時的部分是將產生的結果寫入hive,舉個例子,對3g*1G表的join來講,將結果使用以下方式直接寫入hive表需要超過半小時的時間:dataframe.registerTempTable("result")sql(s"""INSERT OVERWRITE Table $outputTable PARTITION (dt ='$outputDate
  • 一張900w的數據表,17s執行的SQL優化到300ms?
    先說一下背景有一張9555695條數據量的財務流水錶,未做分庫分表,使用limit關鍵字來進行分頁查詢,未做優化前常規查詢需要耗時16 s 938 ms (execution: 16 s 831 ms, fetching: 107 ms),下面我將一步一步講解如何做優化,使得優化後的SQL能在耗時347 ms (execution
  • 一次神奇的 sql 查詢經歷,group by 慢查詢優化記錄
    一、問題背景  現網出現慢查詢,在500萬數量級的情況下,單表查詢速度在30多秒,需要對sql進行優化,sql如下:  思路一:  後面應該加上 order by null;避免無用排序,但其實對結果耗時影響不大,還是很慢。
  • SQL優化系列之 in與range 查詢
    MySQL優化器將in這種方式轉化成 n*m 種組合進行查詢,最終將返回值合併,有點類似union但是更高效。MySQL在 IN() 組合條件過多的時候會發生很多問題。查詢優化可能需要花很多時間,並消耗大量內存。新版本MySQL在組合數超過一定的數量就不進行計劃評估了,這可能導致MySQL不能很好的利用索引。
  • 通過 SQL 查詢學習 Pandas 數據處理
    結合 NumPy 和 Matplotlib 類庫,我們可以在內存中進行高性能的數據清洗、轉換、分析及可視化工作。雖然 Python 本身是一門非常容易學習的語言,但要熟練掌握 Pandas 豐富的 API 接口及正確的使用方式,還是需要投入一定時間的。對於數據開發工程師或分析師而言,SQL 語言是標準的數據查詢工具。
  • 數據分析利器 pandas 系列教程(四):對比 sql 學 pandas
    教程的第四篇,本篇將對比 sql 語言,學習 pandas 中各種類 sql 操作,文章篇幅較長,可以先收藏後食用,但不可以收藏後積灰~為了方便,依然以下面這個 DataFrame 為例,其變量名為 df,設有一同樣結構的 SQL 表,表名為 tb:
  • 我臉都問綠了!二面竟然被問到MySQL時間類型datetime、bigint及timestamp的查詢效率...
    / 如果不想用代碼生成,而是想通過 sql 文件倒入數據,附 sql 文件網盤地址:https://pan.baidu.com/s/1Qp9x6z8CN6puGfg-eNghigsql 查詢速率測試select count(*) from users where time_date >="2018-10-21 23:32:44" and time_date <="2018
  • SQL是一門手藝
    來看其中的關鍵點,union all ,將2個格式一致的select查詢結果合併。子查詢 () t ,用括號包含查詢語句並命名為t,查詢結果是一個表格,這是一個隱含表,可以被select。Join on,把2個表按某個欄位連接起來,把右表中的信息連到左表上。
  • 如何將 MySQL 去重操作優化到極致?|CSDN 博文精選
    外層查詢也要掃描t_source表的100萬行數據,在與導出表做連結時,對t_source表每行的item_id,使用auto_key0索引查找導出表中匹配的行,並在此時優化distinct操作,在找到第一個匹配的行後即停止查找同樣值的動作。
  • 【SQL】詳細講解如何進行Hive性能優化
    Hive 自動執行這種裁剪優化。分區參數為:hive.optimize.pruner=true(默認值為真)3.3JOIN操作在編寫帶有 join 操作的代碼語句時,應該將條目少的表/子查詢放在 Join 操作符的左邊。
  • 一張6000w數據表的查詢優化到0.023s
    詳細需求老大給我安排了個任務,讓我寫個按天分表的定時任務,每次把一天的數據轉移到按天生成的表中,並刪除原表中的數據,主要目的是不想再增長表空間了,保持一個平衡,因為每天刪 500w 也會加 500w。表空間和數據量如下圖:實現思路
  • 如何用 SQL 的方式打開 Pandas?
    不過好消息是,藉助於pandassql,你可以使用SQL來操作DataFrame。# 導入相關庫import numpy as npimport pandas as pdfrom pandasql import sqldf, load_meat, load_births基礎pandasql 中的主要函數是 sqldf,它接收兩個參數:一個SQL 查詢語句;一組會話/環境變量(locals() 或 globals
  • PandaSQL:一個讓你能夠通過SQL語句進行pandas的操作的python包
    如果你熟練的使用SQL,那麼這篇文章將介紹一種更直接、簡單的使用Pandas處理大多數數據操作案例。假設你對SQL非常的熟悉,或者你想有更可讀的代碼。或者您只是想在dataframe上運行一個特殊的SQL查詢。或者,也許你來自R,想要一個sqldf的替代品。
  • 平易近人、兼容並蓄——Spark SQL 1.3.0概覽
    (data, ["name", "age"])# 將Pandas DataFrame轉變為Spark DataFrame(Python API特有功能)sparkDF = sqlContext.createDataFrame(pandasDF)可見,從Hive表,到外部數據源API支持的各種數據源(JSON、Parquet、JDBC),再到RDD乃至各種本地數據集,都可以被方便快捷地加載
  • 提升SQL語句性能的方法
    先用實際案例分析了優化SQL語句的方法,然後再結合nat123這個實際案例分析了如何實現外網訪問內網Mysql資料庫的方法。線上mysql資料庫爆出一個慢查詢,DBA觀察發現,查詢時伺服器IO飆升,IO佔用率達到100%, 執行時間長達7s左右。優化方法:優化的總體思路是拆分sql,將排序操作和查詢所有信息的操作分開。
  • 高效的SQL盲注_位運算(一)
    4 --> 0000 0100 位右移2為變為 0000 0001 (其中紅色0是丟棄掉的高位, 綠色0是補上的低位)到這裡大家應該都明白個大概了sql注入有什麼關係呢?即 0111 001?我們將得到的二進位數(0111 0011)轉換成十進位(115)再轉換成ascii碼對應的字符(s)
  • Jfinal 數據操作模塊 db.record v 0.0.4 發布
    ,似orm非orm,半對像操作。本類庫基於jfinal V1.9改造,寫一些簡單的功能的時候特別適合使用,比如要轉結數據,比如初始數據,相比orm不需要搞一堆對像,相比存sql不需要搞一些insert update。通過sql語句查詢某幾個表的數據返回 record,直接將該對像(不是寫sql)保存或者更新到一個新表中。
  • 最詳細的SQL注入相關的命令整理
    /E:此參數指定註冊表編輯器要進行導出註冊表操作,在此參數後面空一格,輸入導出註冊表的文件名。Regpath:用來指定要導出哪個註冊表的分支,如果不指定,則將導出全部註冊表分支。在這些參數中,"/L:system"和"/R:user"參數是可選項,如果不使用這兩個參數,註冊表編輯器則認為是對WINDOWS目錄下的"system.dat"和"user.dat"文件進行操作。
  • 資料庫性能優化之 SQL 語句優化 1
    :)一、問題的提出在應用系統開發初期,由於開發資料庫數據比較少,對於查詢SQL語句,複雜視圖的的編寫等體會不出SQL語句各種寫法的性能優劣,但是如果將應用系統提交實際應用後,隨著資料庫中數據的增加,系統的響應速度就成為目前系統需要解決的最主要的問題之一。系統優化中一個很重要的方面就是SQL語句的優化。