SQL 查詢優化之 WHERE 和 LIMIT 使用索引的奧秘

2021-02-19 數據結構與算法
www.cnblogs.com/tangyanbo/p/6378741.html

select * from acct_trans_log WHERE  acct_id = 1000000000009000757 
order by create_time desc limit 0,10

select * from acct_trans_log WHERE  acct_id = 1000000000009003061 
order by create_time desc limit 0,10

索引:acct_id,create_time 分別是單列索引,資料庫總數據為500w。通過 acct_id 過濾出來的結果集在 1w 條左右。查詢結果:第一條要5.018s,第二條0.016s為什麼會是這樣的結果呢?第一,acct_id和create_time都有索引,不應該出現5s查詢時間這麼慢啊 仔細觀察會發現,索引只使用了idx_create_time,沒有用到idx_acct_id。這能解釋第一條sql很慢,因為where查詢未用到索引,那麼第二條為什麼這麼快?看起來匪夷所思,其實搞清楚mysql查詢的原理之後,其實很簡單。我們來看這2條sql查詢,都用到了where order by limit。當有limit存在時,查詢的順序就有可能發生變化,這時並不是從資料庫中先通過where過濾再排序再limit。因為如果這樣的話,從500萬數據中通過where過濾就不會是5s了。此時的執行順序是,先根據idx_create_time索引樹,從最右側葉子節點,反序取出n條,然後逐條去跟where條件匹配。若匹配上,則得出一條數據,直至取滿10條為止,為什麼第二條sql要快,因為運氣好,剛好時間倒序的前幾條就全部滿足了。搞清楚原理之後,我們了解了為什麼第一條慢,第二條快的原因,但是問題又來了為什麼mysql不用idx_acct_id索引,這是一個問題,因為這樣的話,我們的建立的索引基本失效了,在此類sql下查詢效率將會是相當低。因為通過acct_id過濾出來的結果集比較大,有上萬條,mysql認為按時間排序如果不用索引,將會是filesort,這樣會很慢,而又不能2個索引都用上,所以選擇了idx_create_time。這裡為什麼不能2個索引都用上,可能很多人也不知道為什麼,其實道理很簡單,每個索引在資料庫中都是一個索引樹,其數據節點存儲了指向實際數據的指針,如果用一個索引來查詢,其原理就是從索引樹上去檢索,並獲得這些指針,然後去取出數據。試想,如果你通過一個索引,得到過濾後的指針,這時,你的另一個條件索引如果再過濾一遍,將得到2組指針的集合,如果這時候取交集,未必就很快,因為如果每個集合都很大的話,取交集的時候,等於掃描2個集合,效率會很低,所以沒法用2個索引。當然有時候mysql會考慮臨時建立一個聯合索引,將2個索引聯合起來用,但是並不是每種情況都能奏效,同樣的道理,用一個索引檢索出結果集之後,排序時,也無法用上另一個索引了。實際上用索引idx_acct_id大多數情況還是要比用索引idx_create_time要快,我們舉個例子:

select * from acct_trans_log force index(idx_acct_id)
WHERE  acct_id = 1000000000009000757 
order by create_time desc limit 0,10

可以看出改情況用idx_acct_id索引是比較快的,那麼是不是這樣就可以了呢,排序未用上索引,始終是有隱患的。

select * from acct_trans_log force index(idx_acct_id)
WHERE  acct_id = 3095  
order by create_time desc limit 0,10

 該sql通過acct_id過濾出來的結果集有100萬條,因此排序將會耗時較高,所幸這裡只是取出前10條最大的然後排序查詢概況,我們發現時間基本消耗在排序上,其實這是內存排序,對內存消耗是很高的。那麼我們有沒有其它解決方案呢,這種sql是我們最常見的,如果處理不好,在大數據量的情況下,耗時以及對資料庫資源的消耗都很高,這是我們所不能接受的,我們的唯一解決方案就是讓where條件和排序欄位都用上索引

alter table acct_trans_log add index idx_acct_id_create_time(acct_id,create_time)

select * from acct_trans_log WHERE  acct_id = 3095  
order by create_time desc limit 0,10

聯合索引讓where條件欄位和排序欄位都用上了索引,問題解決了!但是為什麼能解決這個問題呢,這時大家可能就會記住一個死理,就是聯合索引可以解決where過濾和排序的問題,也不去了解其原理,這樣是不對的,因為當情況發生變化,就懵逼了,

select * from acct_trans_log force index(idx_acct_id_create_time)
WHERE  acct_id in(3095,1000000000009000757)
order by create_time desc limit 0,10

索引還是用idx_acct_id_create_time,時間居然慢下來了。 看執行計劃,排序用到了filesort,也就是說,排序未用到索引。那麼我們還是來看看,索引排序的原理,我們先來看一個sql:

select * from acct_trans_log
ORDER BY create_time limit 0,100

 這裡執行的步驟是,先從索引樹中,按時間升序取出前100條,因為索引是排好序的,直接左序遍歷即可了,因此,這裡mysql並沒有做排序動作,如果想降序,則右序遍歷索引樹,取出100條即可,查詢固然快,那麼聯合索引的時候,是怎樣的呢?

select * from acct_trans_log
WHERE  acct_id = 3095 
order by create_time desc limit 0,10

使用組合索引:idx_acct_id_create_time。這個時候,因為acct_id是聯合索引的前綴,因此可以很快實行檢索,如果sql是

select * from acct_trans_log WHERE  acct_id = 3095

select * from acct_trans_log
WHERE  acct_id = 3095 
order by create_time

如果我們把條件換成order by create_time desc limit 0,10呢?這時候,應該從idx_acct_id_create_time樹右邊葉子節點倒序遍歷,取出前10條即可因為數據的前綴都是3095,後綴是時間升序。那麼我們倒序遍歷出的數據,剛好滿足 order by create_time desc。因此也無需排序。

select * from acct_trans_log force index(idx_acct_id_create_time)
WHERE  acct_id in(3095,1000000000009000757)
order by create_time desc limit 0,10

已知:id1<id2<id3...  time1<time2<time3....索引出來的默認排序是這樣的,id是有序的,時間是無序的,因為有2個id,優先按id排序,時間就是亂的了,這樣排序將會用filesort,這就是慢的原因,也是排序沒有用到索引的原因。type:顯示使用了何種類型,從最好到最差的連接類型為const,eq_ref,ref,range,index,allpossible_keys:顯示可能應用在這張表中的索引。如果為空,沒有可能的索引key:實際使用的索引,如果為null,則沒有使用索引。key_len:使用的索引的長度。在不損失精確性的情況下,長度越短越好ref:顯示索引的哪一列被使用了,如果可能的話,是一個常數rows:mysql認為必須檢查的用來返回請求數據的行數

相關焦點

  • SQL優化系列之 in與range 查詢
    MySQL優化器將in這種方式轉化成 n*m 種組合進行查詢,最終將返回值合併,有點類似union但是更高效。MySQL在 IN() 組合條件過多的時候會發生很多問題。查詢優化可能需要花很多時間,並消耗大量內存。新版本MySQL在組合數超過一定的數量就不進行計劃評估了,這可能導致MySQL不能很好的利用索引。
  • 打造出色查詢:如何優化SQL查詢?
    =和<>可能使索引無效。5.優化limit分頁通常用limits來實現日常分頁,但當偏移量特別大時,查詢效率便會降低。因為Mysql不會跳過偏移量,而是直接獲取數據。· 選項二:使用+索引排序,也可以提高查詢效率。6.優化like語句在日常開發中,如果使用模糊關鍵字查詢,我們很容易想到like,但like可能會使索引無效。
  • MySQL的limit用法和分頁查詢的性能分析及優化
    用法在我們使用查詢語句的時候,經常要返回前幾條或者中間某幾行數據,這個時候怎麼辦呢?二、Mysql的分頁查詢語句的性能分析MySql分頁sql語句,如果和MSSQL的TOP語法相比,那麼MySQL的LIMIT語法要顯得優雅了許多。使用它來分頁是再自然不過的事情了。最基本的分頁方式:SELECT ... FROM ... WHERE ... ORDER BY ... LIMIT ...
  • 使用explain和show profile來分析SQL語句實現優化SQL語句
    SQL語句優化是建立在慢查詢分析的基礎上,通過慢查詢定位有問題的SQL語句,關於慢查詢的介紹及其分析工具,可以參考[mysql慢查詢及慢查詢日誌分析工具]一、通過explain查詢1 用法:explain sql2 作用:用於分析sql語句
  • 一次神奇的 sql 查詢經歷,group by 慢查詢優化記錄
    一、問題背景  現網出現慢查詢,在500萬數量級的情況下,單表查詢速度在30多秒,需要對sql進行優化,sql如下:  思路二:  where條件太複雜,沒索引,導致查詢慢,但我給where條件的所有欄位加上了組合索引,也還是沒用
  • 一張900w的數據表,17s執行的SQL優化到300ms?
    先說一下背景有一張9555695條數據量的財務流水錶,未做分庫分表,使用limit關鍵字來進行分頁查詢,未做優化前常規查詢需要耗時16 s 938 ms (execution: 16 s 831 ms, fetching: 107 ms),下面我將一步一步講解如何做優化,使得優化後的SQL能在耗時347 ms (execution
  • 資料庫SQL腳本優化
    3、儘量用表連接代替子查詢,因為子查詢會在查詢前建立臨時表,查詢完又刪除臨時表,對於結果集大的查詢,性能影響更加明顯4、避免沒有where過濾條件的查詢 (前兩天就看到一個查詢bc_freight(400W+)表裡所有數據,沒有過濾條件,我看到的時候已經執行一萬多秒了)5、sql語句儘可能簡單點
  • MySQL大數據下Limit使用
    對於一直用Oracle的我,今天可是非常詫異,MySQL中同一個函數在不同數量級上的性能居然差距如此之大。    優化的話你可以想方法減小offset,如以下:  Select * From ibmng Where id >=(    Select id From ibmng Order By id limit 1000000,1  ) limit 10  大家一定會看到問題, limit 1000000,1 同樣offset不是一樣大嗎,肯定不能優化
  • Mysql Limit 字句優化
    LIMIT字句常用的語法類似於:LIMIT m,n, 針對不同的情況, MySQL會對查詢做一些優化.Time: 0.240s可以看到遍歷的數量少時, 速度更快, LIMIT操作需要掃描的數據增加, 就需要通過一些方式來有優化.覆蓋索引優化當SELECT的欄位被某個索引覆蓋, 由於不需要回表查詢, 效率少許提升.
  • 面對MySQL 查詢索引失效,程式設計師的六大優化技巧!
    不知道你有沒有碰到過這種情況,一條創建了索引的SQL語句在查詢過程中卻沒有使用索引,或是一條本來可以執行的很快的語句,卻由於MySQL選錯了索引,而導致查詢速度變得很慢?充分優化和利用索引能夠大大提高數據的查詢效率,但是在實際的應用中MySQL可能並不總會選擇合適且效率高的索引。
  • 按照這30條建議優化SQL,性能絕對不會太差
    18//或者分開兩條sql寫:select * from user where userid=1select * from user where age = 18理由:對於or+沒有索引的age這種情況,假設它走了userId的索引,但是走到age查詢條件時,它還得全表掃描,也就是需要三步過程:全表掃描+索引掃描+合併如果它一開始就走全表掃描
  • 女朋友都能看懂的,SQL優化乾貨
    '%老師'優化:在欄位後面使用模糊查詢select * from teacher where name like '李%'如果一定要在欄位開頭模糊查詢,那可以使用INSTR(str,substr)意思是:在字符串str裡面,字符串substr出現的第一個位置(index
  • 《MySQL慢查詢優化》之SQL語句及索引優化
    >本文重點關注於SQL語句及索引優化,關於其他優化方式以及索引原理等,請關注本人《MySQL慢查詢優化》系列博文。possible_keys:此次查詢中可能會被選用的索引,注意這些索引不一定被查詢使用到。key:此次查詢中真正使用到的索引。當為複合索引時,不確定是否被充分使用。
  • 美團開源 SQL 優化工具 SQLAdvisor,與內部版本保持一致
    它基於 MySQL 原生詞法解析,再結合 SQL 中的 where 條件以及欄位選擇度、聚合條件、多表 Join 關係等最終輸出最優的索引優化建議。開發團隊稱目前 SQLAdvisor 在美團內部大量使用,較為成熟、穩定,且開源版本和內部使用版本保持完全一致,希望與業內有類似需求的團隊,一起打造一款優秀的 SQL 優化產品。
  • 這句簡單的 sql ,如何加索引?顛覆了我多年的認知
    請問下面的sql語句,要想加快查詢速度,該怎麼創建索引?以下,以mysql資料庫為準。select * from test where a=? and b>? order by c limit 0,100結果可能會出乎你的意料。我們首先準備一下運行環境,然後按照最左前綴原則和explain關鍵字來進行驗證。結果真是顛覆了我多年的認知。
  • MySQL-SQL優化
    type由上至下,效率越來越高ALL 全表掃描index  索引全掃描range 索引範圍掃描,常用語<,<=,>=,between,in等操作ref 使用非唯一索引掃描或唯一索引前綴掃描,返回單條記錄,常出現在關聯查詢中eq_ref  類似ref,區別在於使用的是唯一索引,使用主鍵的關聯查詢
  • MySQL 用 limit 會影響性能?
    我們看一下select * from test where val=4 limit 300000,5;的查詢過程:查詢到索引葉子節點數據。根據葉子節點上的主鍵值去聚簇索引上查詢需要的全部欄位值。類似於下面這張圖:
  • mysql┃多個角度說明sql優化,讓你吊打面試官!
    sql優化,最近moon一直在寫關於mysql的文章,包括之前寫的索引相關,其實也都是為了這篇文章做個鋪墊,所以你懂了嗎,今天我將從表結構、索引、查詢語句、分庫分表這四個維度來和大家聊聊,在工作中,怎麼進行sql優化?
  • 小心避坑:MySQL分頁時使用 limit+order by 會出現數據重複問題
    SQL查詢的時候,很有可能出現和LIMIT 0,5相同的某條記錄。但是事實就是,MySQL再order by和limit混用的時候,出現了排序的混亂情況。2、分析問題在MySQL 5.6的版本上,優化器在遇到order by limit語句的時候,做了一個優化,即 使用了priority queue。
  • MySQL 用 limit 為什麼會影響性能?
    我們看一下select * from test where val=4 limit 300000,5;的查詢過程:查詢到索引葉子節點數據。根據葉子節點上的主鍵值去聚簇索引上查詢需要的全部欄位值。類似於下面這張圖:像上面這樣,需要查詢300005次索引節點,查詢300005次聚簇索引的數據,最後再將結果過濾掉前300000條,取出最後5條。MySQL耗費了大量隨機I/O在查詢聚簇索引的數據上,而有300000次隨機I/O查詢到的數據是不會出現在結果集當中的。推薦:MySQL 索引B+樹原理,以及建索引的幾大原則。