MySQL之索引優化 二

2021-10-11 00:01:35 字數 4366 閱讀 8924

例:

select

*from employees limit

10000,10

;

表示從表 employees 中取出從 10001 行開始的 10 行記錄

實際這條 sql 是先讀取 10010 條記錄,然後拋棄前 10000 條記錄,然後返回後面 10 條想要的資料。因此要查詢一張大表比較靠後的資料,執行效率是非常低的

1. 根據自增且連續的主鍵排序的分頁查詢

select

*from employees limit

90000,5

;

該 sql 表示查詢從第 90001開始的五行資料,因 為主鍵是自增並且連續的,所以可以改寫成按照主鍵去查詢從第 90001開始的五行資料

改寫:

select

*from employees where id >

90000

limit

5;

注意:如果主鍵不連續(比如主鍵id是1-1000的表,中間刪去幾條資料),不能使用上面描述的優化方法。 另外如果原 sql 是 order by 非主鍵的字段,按照上面說的方法改寫會導致兩條 sql 的結果不一致。

這種優化得滿足以下兩個條件:

2. 根據非主鍵字段排序的分頁查詢

例:

select

*from employees order

by name limit

90000,5

;

用explain分析時,顯示並沒有用到name欄位的索引,而是走檔案掃瞄

原因可能是mysql分析掃瞄索引樹的成本可能比全表掃瞄要高,所以放棄使用索引

優化思路:

讓排序時返回的字段盡可能少,所以可以讓排序和分頁操作先查出主鍵,然後根據主鍵查到對應的記錄

select

*from employees e inner

join

(select id from employees order

by name limit

90000,5

) ed on e.id = ed.id;

explain顯示,優化後的 sql 使用的是索引排序

mysql的表關聯常見有兩種演算法:

2.1 巢狀迴圈連線 nested-loop join(nlj) 演算法

一次一行迴圈地從第一張表(稱為驅動表)中讀取行,在這行資料中取到關聯字段,根據關聯欄位在另一張表(被驅動 表)裡取出滿足條件的行,然後取出兩張表的結果合集

例:

explain

sql執行流程:

從表 t2 中讀取一行資料(如果t2表有查詢過濾條件的,會從過濾結果裡取出一行資料)

從第 1 步的資料中,取出關聯字段 a,到表 t1 中查詢

取出表 t1 中滿足條件的行,跟 t2 中獲取到的結果合併,作為結果返回給客戶端

重複上面 3 步

整個過程會讀取 t2 表的所有資料(t2表有100行資料,掃瞄100行),然後遍歷這每行資料中欄位 a 的值,根據 t2 表中 a 的值索引掃瞄 t1 表中的對應行(掃瞄100次 t1 表的索引,1次掃瞄可以認為最終只掃瞄 t1 表一行完整資料,也就是總共 t1 表也掃瞄了100 行)。因此整個過程掃瞄了 200(t2表的掃瞄次數100 + t1 表的掃瞄次數100) 行。

2.2 基於塊的巢狀迴圈連線 block nested-loop join(bnl)演算法

把驅動表的資料讀入到 join_buffer 中,然後掃瞄被驅動表,把被驅動表每一行取出來跟 join_buffer 中的資料做對比

例:

explain

extra 中 的using join buffer (block nested loop)說明該關聯查詢使用的是 bnl 演算法

sql執行流程:

把 t2 的所有資料放入到 join_buffer 中

把錶 t1 中每一行取出來,跟 join_buffer 中的資料做對比

返回滿足 join 條件的資料

整個過程對錶 t1 和 t2 都做了一次全表掃瞄,因此掃瞄的總行數為10000(表 t1 的資料總量) + 100(表 t2 的資料總量) = 10100。並且 join_buffer 裡的資料是無序的,因此對錶 t1 中的每一行,都要做 100 次判斷,所以記憶體中的判斷次數是 100 * 10000= 100 萬次。

join_buffer 的大小是由引數 join_buffer_size 設定的,預設值是 256k。如果放不下表 t2 的所有資料話,策略很簡單, 就是分段放

例:比如 t2 表有1000行記錄, join_buffer 一次只能放800行資料,那麼執行過程就是先往 join_buffer 裡放800行記錄,然 後從 t1 表裡取資料跟 join_buffer 中資料對比得到部分結果,然後清空 join_buffer ,再放入 t2 表剩餘200行記錄,再 次從 t1 表裡取資料跟 join_buffer 中資料對比。所以就多掃了一次 t1 表

問題:被驅動表的關聯欄位沒索引為什麼要選擇使用 bnl 演算法而不使用 nested-loop join 呢?

如果上面第二條sql使用 nested-loop join,那麼掃瞄行數為 100 * 10000 = 100萬次,這個是磁碟掃瞄。 很顯然,用bnl磁碟掃瞄次數少很多,相比於磁碟掃瞄,bnl的記憶體計算會快得多。

因此mysql對於被驅動表的關聯欄位沒索引的關聯查詢,一般都會使用 bnl 演算法。如果有索引一般選擇 nlj 演算法,有索引的情況下 nlj 演算法比 bnl演算法效能更高

2.3 對於關聯sql的優化

關聯欄位加索引,讓mysql做join操作時盡量選擇nlj演算法

小表驅動大表,寫多表連線sql時如果明確知道哪張表是小表可以用straight_join寫法固定連線驅動方式,省去 mysql優化器自己判斷的時間

straight_join:straight_join功能同join類似,但能讓左邊的表來驅動右邊的表,能改表優化器對於聯表查詢的執行順序。

注意:

2.4in和exsits優化

原則:小表驅動大表,即小的資料集驅動大的資料集

in:當b表的資料集小於a表的資料集時,in優於exists

select

*from a where id in

(select id from b)

exists:當a表的資料集小於b表的資料集時,exists優於in

select

*from a where

exists

(select

1from b where b.id = a.id)

欄位有索引的情況:

count(*)≈count(1)>count(字段)>count(主鍵 id)

欄位有索引,count(字段)統計走二級索引,二級索引儲存資料比主鍵索引少,所以count(字段)>count(主鍵 id)

欄位無索引的情況:

count(*)≈count(1)>count(主鍵 id)>count(字段)

字段沒有索引count(字段)統計走不了索引, count(主鍵 id)還可以走主鍵索引,所以count(主鍵 id)>count(字段)

優化方法:

查詢mysql自己維護的總行數

show table status(表總行數的估計值)

將總數維護到redis裡

增加資料庫計數表

MySQL優化 之 索引

四種索引 主鍵索引,唯一索引,普通索引,全文索引 對查詢語句會提高效率 對增刪改語句會降低效率,因為還要對索引進行增刪改!建立索引會佔磁碟空間 對頻繁查詢的字段應建立索引,對頻繁更新的字段不適合建立索引 1 新增 1.1 主鍵索引新增 1.當一張表,把某個列設為主鍵的時候,則該列就是主鍵索引 cre...

Mysql優化之索引

索引其實就是乙個檔案,它與mysql資料檔案不一樣的地方是 它是順序的儲存資料,檔案小且儲存的位置也不一樣 索引能加快檢索,但系統每一次維護資料 寫入 更新 的同時也需要維護索引,帶來額外的開銷。索引按照底層實現方式分為 b樹索引 r樹索引 雜湊索引等 索引按照具體表現分為 主鍵索引 primary...

資料庫優化之MySQL優化 二 索引優化

索引優化 分類 普通索引,唯一索引,主鍵索引,聯合索引 問題 如何選擇合適的列建立索引?1 在where從句,group by從句,order by從句,on從句中出現的列 2 索引字段越小越好 3 離散度大的列放到聯合索引的前面 select from payment where staff id...