文章目錄
- 情況一:連接數過小
- 情況二:Buffer Pool 太小
MySQL 查詢慢除了索引還能因為什么?MySQL 查詢慢,我們一般也會想到是因為索引,但除了索引還有哪些原因會導致數據庫查詢變慢呢?
以下以 MySQL 中一條 SQL 的執行流程為基礎,分析 MySQL 查詢慢除了索引還有哪些原因。
當 MySQL 中一條查詢 SQL 在實際進入影響 SQL 執行效率的流程前(主要是優化器流程與執行器流程),首先會進入分析器流程。以 Python 進程為例,以下舉例一條 MySQL 語句執行下來會經歷哪些流程。
例如在 MySQL 中有一張名為 use_info
的數據表,一個 Python 進程嘗試攜帶賬號密碼等信息嘗試向 MySQL 建立一條網絡連接,而 MySQL 的連接管理模塊會對這條連接進行管理。
在連接被建立后,Python 應用嘗試向 MySQL 服務器執行如下 SQL 查詢語句:
SELECT user_name,user_address FROM user_info where user_id = 1;
此時 Python 進程需要將 SQL 語句通過網絡連接給 MySQL,MySQL 收到 SQL 語句后將在分析器中先判斷一下 SQL 語句有沒有語法錯誤。例如 SELECT 是否少寫個 L:
SEECT user_name,user_address FROM user_info where user_id = 1;
如果 SQL 錯誤,SELECT 中確實少寫個 L,將拋出相關的異常提示:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'SEECT user_name,user_address FROM user_info where user_id = 1' at line 1
SQL 在分析器過程中執行無誤后將到達優化器,而優化器會根據一些規則選擇需要使用的索引,之后執行器會調用存儲引擎的接口函數,MySQL 中的存儲引擎是MySQL 中真正負責讀寫數據的組件。在如今的 MySQL 數據庫開發中,最常用的存儲引擎就是 InnoDB 存儲引擎。
由于讀寫磁盤較慢,所以 InnoDB 存儲引擎內部增加了一層名為 Buffer Pool 的內存提速設計, 在 Buffer Pool 中即存放行數據又存放索引數據。查詢 SQL 到了 InnoDB 中會根據前面優化器里計算得到的索引去查詢相應的索引頁,如果索引頁不在 Buffer Pool 里,則從磁盤里加載到索引頁,再通過索引頁查詢得到數據頁的位置。如果這些數據頁不在 Buffer Pool 中,則從磁盤里加載進來,最后將得到的一行行數據結果返回給客戶端。
在優化器流程與執行器流程過程中,數據庫慢查詢一般是優化器選錯索引導致。這類問題可以通過 EXPLAIN 命令排查。
但是,除了索引之外,還有哪些因素會限制查詢速度呢?
情況一:連接數過小
MySQL 的連接管理模塊作用是管理客戶端和 MySQL 之間的長連接,假設兩者之間只有一條連接,那么在執行 SQL 查詢之后只能阻塞等待結果返回,如果有大量查詢同時并發請求,那么后面的請求都需要等待前面的請求執行完成后才能開始執行。因此有時候從應用程序的日志看,有些 SQL 執行了幾分鐘,但將 SQL 單獨拎出來執行卻只有幾毫秒的情況。對于這種情況,實際上就是因為這些 SQL 語句在等待前面的 SQL 執行完成。
那么這個問題該如何解決呢?其實多建立一些連接就可以解決這個問題,多建一些連接目的是讓請求能夠并發執行,從而使后面的連接不需要等待那么久。但需要注意的是,連接數過小的問題受數據庫和客戶端兩側同時限制。
-
數據庫連接數過小的情況:
MySQL 的最大連接數默認是100,最大可以達到 16384,可以通過如下命令將 SQL 的最大連接數改為500。
SET GLOBAL max_connections = 500;
查看 MySQL 最大連接數配置命令:
SHOW GLOBAL VARIABLES LIKE 'max_connections';
-
應用側連接數過小的情況:
如果數據庫連接大小是調整過了,但卻沒啥效果,那可能是因為服務應用(如 Java 或 Python 應用等)的連接數也過小。應用側與 MySQL 底層的連接是基于 TCP 協議的長連接,而建立長連接比較耗時,所以通常情況下會維護一個長連接池,要執行 SQL 時從里面撈出一條連接出來用,用完塞回去,下次復用。
需要注意的是,連接池的容量會有上限,連接池容量的上限指的是連接池能夠控制的連接數量,如果連接池容量的上限太低,那么修改連接池最大連接數也沒什么作用。
如果需要調大這個連接池該如何調呢?在實際編碼中,通常服務應用(如 Java 或 Python 應用等)都會通過 ORM 庫進行讀寫操作,而成熟的 ORM 庫會有個連接池的配置,按照官方文檔改就好。
情況二:Buffer Pool 太小
Buffer Pool 太小也會導致 MySQL 查詢慢的問題。在前文提到的 InnoDB 存儲引擎中里有一層內存 Buffer Pool,Buffer Pool 通過緩存磁盤數據用于加速查詢,如果 Buffer Pool 越大,那么 Buffer Pool 中能夠存放的數據頁就越多,相應的 SQL 查詢時就更可能命中 Buffer Pool,那么 MySQL 查詢速度自然更快。
可以執行下面命令增大 Buffer Pool 的大小:
SET GLOBAL innodb_buffer_pool_size = 536870912;
innodb_buffer_pool_size
是 MySQL 中用于設置 Buffer Pool 的參數,它的單位為字節。上例中,將 innodb_buffer_pool_size
的值設置為 536870912 即將將 InnoDB 緩沖池的大小為 536870912 字節,換算成兆字節(MB)為 521 MB(因為 1 MB = 1024 * 1024 個字節)。
查詢 innodb_buffer_pool_size
的大小命令:
SHOW GLOBAL VARIABLES LIKE 'innodb_buffer_pool_size';
輸出的結果例如:
但是如果數據庫查詢慢并不是由于 Buffer Pool 的大小導致,那么修改 Buffer Pool 的大小就毫無意義。那么如何判斷 Buffer Pool 是不是太小了?可以通過查看 Buffer Pool 的命中率來分析。
可以通過如下命令查詢 Buffer Pool 的一些相關信息:
SHOW STATUS LIKE 'Innodb_buffer_pool_%';
得到的 Buffer Pool 相關信息例如:
其中 Innodb_buffer_pool_read_requests
表示請求的次數,Innodb_buffer_pool_reads
表示從物理磁盤中讀數據的請求次數。
Buffer Pool 的命中率可以通過如下公式計算:
1-(Innodb_buffer_pool_reads/Innodb_buffer_pool_read_requests)*100%
例如上述 Buffer Pool 的命中率為:
1-(68759212/4966742025)*100% ≈ 98.6156%
一般情況下,Buffer Pool 的命中率都在 99% 以上,如果低于 99% 才需要考慮加大 Buffer Pool 的大小。