1. 程式人生 > >高性能MySQL——查詢性能優化

高性能MySQL——查詢性能優化

between 無法 行數 不同 last 完全 可擴展 結果集 name

在數據庫設計中,如果查詢寫得很糟糕,即使庫表結構設計再合理,索引再合理也無法實現高性能。

1、優化數據訪問

查詢性能低下最基本的原因是訪問的數據太多。對於低效的查詢,通過以下兩步來分析總是很有效:

  1. 確認應用程序是否在檢索大量超過需要的數據。這通常意味著訪問了太多行或者太多列;
  2. 確認MySQL服務器層是否在分析大量超過需要的數據行。

  是否向數據庫請求了不需要的數據

有些查詢會請求超過實際需要的數據,然後被應用程序丟棄,這會增加MySQL服務器的額外負擔,也會增加網絡開銷。

典型案例:

  1. 查詢不需要的記錄。例如:查詢表中所有數據,只顯示了前10條。最簡單有效的辦法——在這樣的查詢後面加上LIMIT;
  2. 多表關聯時返回全部列。例如:SETECT * FROM actor INNER JOIN film_actor USING(actor_id) ...; 改進:SELCET actor.* FROM actor INNER JOIN film_actor USING(actor_id) ...;只取出需要的列;
  3. 總是取出全部列。取出全部列,會讓優化器無法優化器無法完成索引覆蓋掃描這類優化,還會為服務器帶來額外的I/O、CPU和內存消耗。因此一些DBA甚至嚴格禁止SELECT *這種寫法;
  4. 重復查詢相同的數據。

  MySQL是否掃描額外的記錄

對於MySQL,最簡單的衡量查詢開銷的三個指標:響應時間;掃描行數;返回行數。

一般MySQL能夠使用三種方式應用where條件,從優到劣依次為:

  1. 在索引中使用where條件來過濾不匹配的記錄,這在存儲引擎層完成;
  2. 使用索引覆蓋掃描來返回記錄,直接從索引中過濾不需要的記錄,並返回命中結果。這在MySQL服務器層完成,但無需回表查詢;
  3. 從數據表中返回數據,然後過濾不滿足條件的記錄。這在MySQL服務器層完成,需要從數據表讀出所有記錄然後過濾。

如果發現查詢需要掃描大量數據,但是只返回少量數據時,可以嘗試如下方法去優化它:

  1. 使用索引覆蓋掃描,把所有需要用到的列都放到索引中,這樣存儲引擎無需回表獲取對應行數據;
  2. 改變庫表結構。例如:使用單獨的匯總表;
  3. 重寫次復雜查詢,讓MySQL優化器能夠以更優的方式去執行它。

2、重構查詢的方式

  1. 一個復雜查詢還是多個簡單查詢。設計查詢的時候,是否需要將一個復雜查詢分成多個簡單查詢,這是一個需要考慮的重要問題;
  2. 切分查詢——將一個大查詢切分成多個小查詢,每個查詢完全一樣,每次只返回一部分結果。例如:一次需要刪除1000w條數據,如果切分為每次刪除1w條,分多次刪除,這樣會大大減輕MySQL服務器的負擔。DELETE FROM messages WHERE ...;
  3. 分解關聯查詢——用分解關聯查詢的方式重構查詢有如下優勢:
    • 讓緩存的效率更高。許多引用程序可以方便的緩存單表查詢對應的結果對象;
    • 將查詢分解後,執行單個查詢可以減少鎖的爭用;
    • 在應用層做關聯,可以更容易對數據庫做拆分,更容易做到高性能和可擴展;
    • 查詢本身效率也可能會有所提升。例如:使用IN()代替關聯查詢,可以讓MySQL按照ID順序進行查詢,這可能比隨機關聯更高效;
    • 可以減少冗余記錄的查詢。

3、查詢執行的基礎(略)

4、MySQL查詢優化器的局限性

  關聯子查詢

例如:SELECT * FROM film WHERE film_id IN(SELECT film_id FROM film_actor WHERE actor_id = 1); MySQL優化器優化後:SELECT * FROM film WHERE EXISTS(SELECT film_id FROM film_actor WHERE actor_id = 1 AND film_actor.film_id = film.film_id);

從優化器優化後的結果可以看出,MySQL會將相關外層表壓到子查詢中,此時子查詢需要根據“film_id”來關聯外部表film,因為需要“film_id”字段,MySQL認為無法執行子查詢,所以會對film表執行全表掃描,如此性能就非常低了。

優化方案:通過關聯查詢來改寫關聯子查詢。例如:SELECT film.* FROM film INNER JOIN film_actor USING(film_id) WHERE actor_id = 1;

  UNION的限制

有時候MySQL不能將限制條件從外層下到內層。例如:希望UNION的各個子句能夠根據LIMIT只取部分結果集,或希望先排序再合並結果集,就需要在UNION的各個子句分別使用這些子句。

如:(SELECT first_name, last_name FROM actor ) UNION (SELECT first_name, last_name FROM customer) ORDER BY last_name LIMIT 20;

優化方案:(SELECT first_name, last_name FROM actor ORDER BY last_name LIMIT 20) UNION (SELECT first_name, last_name FROM customer ORDER BY last_name LIMIT 20) LIMIT 20;

  松散索引掃描

由於歷史原因,MySQL不支持松散索引掃描,也就無法按照不連續的方式掃描一個索引。

例如:存在索引key(a, b);執行查詢SELECT * FROM table WHERE b BETWEEN 2 AND 5; 因為索引的前導字段是列a,但查詢中只指定了b,顧MySQL無法使用這個索引;

優化方案:給前導列加上可能的常數值。SELECT * FROM table WHERE a IN (2, 3, 5, ...) AND b BETWEEN 2 AND 5;

  在同一張表上查詢和更新

MySQL不允許對同一張表同時進行查詢和更新。可以通過生成臨時表的形式來繞過此限制。

例如:UPDATE tbl AS outer_tbl SET cnt = (SELECT count(*) FROM tbl AS inner_tbl WHERE inner_tbl.type = outer_tbl.type);

優化方案:UPDATE tbl INNER JOIN(SELECT type, count(*) AS cnt FROM tbl GROUP BY type) SET tbl.cnt = der.cnt;

5、查詢優化器的提示

如果對優化器選擇的執行計劃不滿意,可以使用優化器提供的幾個提示(hint)來控制最終執行計劃。具體用法可查閱MySQL官方手冊。

6、優化特定類型的查詢

  優化count()查詢

count()的作用:count()是一個特殊的聚合函數,有兩種非常不同的用法:統計某列值的數量;統計行數。在統計列值時,要求列值時非空的(不統計null)。如果count()括號中時某列或某列的表達式,則統計的是這個表達式有值的結果數。

高性能MySQL——查詢性能優化