mysql 簡單進階 ———— 重構查詢[二]

前言

簡單整理一下重構查詢。

正文

爲什麼我們需要重構查詢,原因也很簡單,那就是查詢慢。

爲什麼會查詢慢?

查詢性能慢底下的最基本的原因是訪問的數據太多。 某些查詢不可避免地需要篩選大量的數據,但這並不常見。

大部分性能低下的查詢都可以通過減少訪問的數據流的方式進行優化。

  1. 確認應用程序是否檢索大量超過需要的數據。這意味着訪問了太多的行,但有時候也可能訪問了太多的列。
  2. 確認mysql 服務器是否在分析大量超過需要的數據行。

最先想到的是是否向數據庫請求了不需要的數據。

舉個很低級的例子:

  1. 查詢了多餘的行
在c#的linq中通過tolist在執行了 "select * from payment",然後通過take 和 skip 來獲取需要的數據,這個時候其實是查詢了全部的行,然後纔在應用層進行過濾了。
  1. 查詢了多餘的列

比如select * from payment中,通過查詢了全部的列,但是很多列是不需要的。

但是這個也不能以偏概全,要看應用場景,比如查詢出來做緩存,那麼可能只查詢了一次,然後用的是緩存的數據,但是通過查詢多餘的列出來可以供多個地方使用。

  1. 重複查詢了通用的數據

比如一個數據高頻訪問,低更新,那麼就應該考慮使用緩存了,這樣避免大量的查詢。

還有一個重要的指標,mysql 是否掃描了額外的記錄。

是否掃描了額外的記錄,有下面3個標準:

  1. 響應的時間

  2. 掃描的行數

  3. 返回的行數

沒有哪個指標能夠完美地衡量查詢的開銷,但是他們能夠反映了mysql在內部執行查詢時候需要訪問的數據,並可以大概推算出運行的時間。

這三個指標都會記錄到mysql的慢日誌中,所以檢查慢日誌記錄是找出掃描行數過多的查詢的好辦法。

  1. 響應時間

響應時間包括兩個,一個是服務時間,一個是排隊時間。

服務時間是指數據庫處理這個查詢真正花了多長時間。排隊時間是指因爲等待某些資源而沒有真正的執行查詢的時間,可能是等i/o 操作完成,也可能是等行鎖。

  1. 掃描的行數和返回的行數

最理想的情況是掃描的行數等於返回的行數,這個就太理想了。一般是在1:1,和10:1之間,那麼就是要儘量減少掃描的行數。

  1. 掃描的行數和訪問的類型

這個訪問的類型指的是explain 後,返回的類型。

1. id:表示查詢執行計劃中的每個步驟的唯一標識符。id的值越大,表示該步驟的執行順序越靠後。

2. select_type:表示該步驟的查詢類型。常見的查詢類型包括SIMPLE(簡單查詢)、PRIMARY(主查詢)、SUBQUERY(子查詢)等。

3. table:表示該步驟涉及的表名。

4. partitions:表示該步驟涉及的分區。

5. type:表示該步驟使用的訪問方法,也稱爲連接類型。常見的訪問方法包括ALL(全表掃描)、index(使用索引掃描)、range(範圍掃描)等。

6. possible_keys:表示該步驟可能使用的索引。

7. key:表示該步驟實際使用的索引。

8. key_len:表示該步驟使用的索引的長度。

9. ref:表示該步驟使用的索引的列與前一個步驟的關聯條件。

10. rows:表示該步驟掃描的行數。

11. filtered:表示該步驟滿足WHERE條件的行數佔總掃描行數的比例。

12. Extra:表示該步驟的額外信息。常見的額外信息包括Using filesort(使用文件排序)、Using temporary(使用臨時表)等。

上面說一下可能難以理解的幾個元素:

key_len字段表示使用的索引的長度,它表示索引中被使用的字節數。索引長度越短,查詢性能通常越好。

舉個例子來說明key_len的含義:

假設有一個表table,其中有一個列name定義爲VARCHAR(100),並且有一個非唯一索引idx_name(name)。如果在查詢中使用了以下條件:

SELECT * FROM table WHERE name = 'John'

那麼在執行EXPLAIN語句後,可以看到key_len字段的值爲100。這是因爲查詢條件中的name列的長度爲100,索引idx_name的長度也爲100,所以key_len的值爲100。

另外,如果查詢條件中使用了name列的前綴,比如:

SELECT * FROM table WHERE name LIKE 'J%'

那麼在執行EXPLAIN語句後,可以看到key_len字段的值會根據前綴的長度而變化。如果前綴長度爲1,則key_len的值爲1。如果前綴長度爲2,則key_len的值爲2。以此類推。

需要注意的是,key_len並不是表示索引的實際長度(比如字節數),而是表示索引中被使用的字節數。它可以用於比較不同查詢條件對索引的利用程度,從而進行索引的優化。

這裏很多人理解的可能就以爲是索引的長度,其實是實際使用的索引的長度。

還有一個是ref:

在MySQL的EXPLAIN輸出中,ref是一個關鍵字段,表示查詢過程中使用的索引列與上一個表的列之間的引用關係。ref字段通常用於連接操作(JOIN)中,指示連接條件使用的索引列。

以下是一個具體的例子來解釋ref字段的含義:

假設有兩個表table1和table2,它們的結構如下:

table1:
- id (主鍵)
- name

table2:
- id (主鍵)
- table1_id (外鍵)

現在我們執行以下查詢語句:

SELECT * FROM table1 JOIN table2 ON table1.id = table2.table1_id WHERE table1.name = 'John'

在執行EXPLAIN語句後,可以看到以下輸出:

id | select_type | table | partitions | type | possible_keys | key           | key_len | ref              | rows | Extra
1  | SIMPLE      | table1| NULL       | ref  | PRIMARY       | PRIMARY       | 4       | const            | 1    | Using index
1  | SIMPLE      | table2| NULL       | ref  | table1_id     | table1_id     | 4       | table1.id        | 2    | Using index

- ref字段:在這個例子中,第二行的ref字段的值爲table1.id,表示查詢過程中使用了table1表的id列作爲索引列與table2表的table1_id列進行連接。
- key字段:key字段的值爲table1_id,表示查詢使用了名爲table1_id的索引。
- rows字段:rows字段的值爲2,表示MySQL估計需要掃描的行數爲2。

在這個例子中,我們可以看到ref字段指示了連接操作中使用的索引列之間的引用關係。通過理解ref字段的含義,我們可以更好地理解連接操作的執行過程,並進行性能優化和調整。

在MySQL的EXPLAIN輸出中,當ref字段的值爲const時,表示查詢過程中使用的索引列與一個常量值之間的引用關係。這意味着查詢使用了一個常量值來匹配索引列。

以下是一個具體的例子來解釋ref字段爲const的含義:

假設有一個表table,其中有兩個列id和name,並且有一個唯一索引idx_name(name)。現在我們執行以下查詢語句:

SELECT * FROM table WHERE name = 'John'

在執行EXPLAIN語句後,可以看到以下輸出:

id | select_type | table | partitions | type | possible_keys | key      | key_len | ref   | rows | Extra
1  | SIMPLE      | table | NULL       | ref  | idx_name      | idx_name | 102     | const | 1    | Using index

- ref字段:在這個例子中,ref字段的值爲const,表示查詢過程中使用的索引列與常量值之間的引用關係。
- key字段:key字段的值爲idx_name,表示查詢使用了名爲idx_name的索引。
- rows字段:rows字段的值爲1,表示MySQL估計需要掃描的行數爲1。

在這個例子中,ref字段爲const表示查詢使用了常量值來匹配索引列。這樣的查詢通常會非常高效,因爲MySQL可以直接使用索引來定位匹配的行,而不需要進一步的比較操作。

總結來說,當ref字段爲const時,表示查詢使用了一個常量值來匹配索引列,這通常會帶來高效的查詢性能。

除了const之外,ref字段可能還有其他的取值,具體取決於查詢語句和索引的使用情況。以下是一些常見的ref取值:

1. ref: 當查詢使用索引列與另一個表的列進行連接時,ref字段的值可能是另一個表的列名。例如,在連接操作(JOIN)中,如果使用了索引列與另一個表的列進行連接,ref字段可能顯示連接條件中使用的列名。

2. NULL: 當查詢不使用索引列與其他表進行連接時,或者查詢沒有使用索引時,ref字段的值可能爲NULL。這表示查詢沒有引用其他表的列。

3. Multiple values: 在某些情況下,ref字段可能會顯示多個值,表示查詢使用了多個索引列與其他表的多個列進行連接。這通常發生在複雜的連接操作中。

請注意,ref字段的具體取值取決於查詢語句、索引的使用和表之間的關係。因此,在不同的查詢中,ref字段可能會有不同的取值。瞭解和理解ref字段的含義可以幫助我們更好地理解查詢執行過程,並進行性能優化和調整。

當查詢不使用任何索引或者沒有引用其他表的列時,ref字段的值可能爲NULL。這種情況下,查詢只涉及到單個表,沒有進行連接操作或者索引的使用。

以下是一個示例來說明ref字段爲NULL的情況:

假設有一個表table,其中有兩個列id和name。我們執行以下查詢語句:

SELECT * FROM table WHERE id = 1

在執行EXPLAIN語句後,可以看到以下輸出:

id | select_type | table | partitions | type | possible_keys | key  | key_len | ref | rows | Extra
1  | SIMPLE      | table | NULL       | ALL  | NULL          | NULL | NULL    | NULL| 1    | NULL

- ref字段:在這個例子中,ref字段的值爲NULL,表示查詢沒有引用其他表的列,也沒有使用任何索引。

這種情況下,ref字段爲NULL表示查詢只涉及到了單個表,沒有進行連接操作或者使用索引。這樣的查詢可能會導致全表掃描,對於大表來說性能較差。在優化查詢性能時,可以考慮添加適當的索引或者修改查詢語句來避免全表掃描。

那麼這個type 有全表掃描、範圍掃描、唯一索引查詢、常數引用等。

舉個例子:

EXPLAIN select film_id, actor_id
from sakila.film_actor
where  film_id =1;

這裏的type 就是通過ref來實現了,也就是通過索引來實現。

如果沒有索引會變成下面這樣:

就是進行了全表掃描了。掃描了5073行,但是得到了才10行。

type 有下面這些類型:

1. system:表示只有一行的表,這是const類型的一個特例。例如,SELECT * FROM table WHERE primary_key = 1。

2. const:表示通過索引一次就能找到的常量值查詢。例如,SELECT * FROM table WHERE primary_key = 1。

3. eq_ref:表示使用唯一索引查找。例如,SELECT * FROM table1 JOIN table2 ON table1.key = table2.key。

4. ref:表示使用非唯一索引查找。例如,SELECT * FROM table1 WHERE key = 'value'。

5. range:表示使用索引範圍查找。例如,SELECT * FROM table WHERE key BETWEEN 1 AND 10。

6. index:表示全索引掃描。例如,SELECT * FROM table WHERE key LIKE 'value%'。

7. all:表示全表掃描。例如,SELECT * FROM table。

8. index_merge:表示使用多個索引合併的結果。例如,SELECT * FROM table WHERE key1 = 'value' OR key2 = 'value'。

9. unique_subquery:表示使用子查詢的結果進行唯一性判斷。例如,SELECT * FROM table WHERE key = (SELECT key FROM other_table)。

10. index_subquery:表示使用子查詢的結果進行索引查找。例如,SELECT * FROM table WHERE key IN (SELECT key FROM other_table)。

那麼如果我們遇到了一個複雜的查詢我們應該如何處理呢?

  1. 可以將一個複雜的查詢分成幾個簡單的查詢,這樣掃描的行數,查詢的複雜度可能會降低,那麼效率就有可能更高。

  2. 切分查詢

舉一個工作中遇到的刪除的例子。比如要刪除3個月前並且標記爲deleted的數據,那麼通過語句直接刪除是不可能的。

那麼可以通過一天一天的查找刪除,並且每次刪除固定的條數比如50條,這樣每次掃描的索引會很少。 其實就是將大的操作,花費的時間分配到更小粒度裏面去了。

  1. 分解關聯查詢

該系列持續更新。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章