你須要掌握的 Mysql 優化的一些要點

本文是學習《高性能 Mysql》中關於 Mysql 中查詢優化須要注意的一些要點的總結:mysql

Schema 和數據類型優化

  • 儘可能避免使用 NULL 值,尤爲存在索引時,由於若是 NULL 列是索引,索引統計以及值的比較更加複雜
  • 儘可能選擇小的簡單的數據類型,由於它們佔用更少的磁盤,內存和 CPU 緩存
  • 儘可能使用 TIMESTAMP 代替 DATETIME,由於 TIMESTAMP 只是 DATETIME 一半大小存儲空間,還會跟時區變化,可是 TIMESTAMP 容許的時間範圍比較小(1970年~2038年)
  • 對於字符串列的最大長度比平均長度大不少的狀況建議使用 VARCHAR 類型
  • 對於很是短且比較均衡的列建議使用 CHAR 類型,不容易產生太多的碎片
  • Mysql 對於 BLOB 和 TEXT 類型的排序和其它類型規則不一樣,只會對每一個列的前 max_sort_length 字節的字符串進行排序,這樣必然會使用臨時表,因此儘可能確保 max_sort_length 的值下不要超過 max_heap_table_size 或者 max_table_size,以保證排序時使用內存臨時表
  • 不一樣類型字段進行關聯查詢時每每成本比較高,建議若是須要關聯查詢儘可能改成相同類型
  • 在查詢時儘可能不要使用太多的關聯,雖然 Mysql 限制了每一個關聯操做最多隻能有 61 張表,可是爲了讓查詢執行的速度快且併發性好,單個查詢不要超過 12 張表關聯
  • 除非枚舉值是一些固定不變的值,例如「性別」,建議不要過分使用枚舉,由於在修改枚舉值時須要 ALTER TABLE 成本很是高,並且枚舉值的排序是按照枚舉順序來排序,並非字面值

索引優化

  • 若是查詢中某個列是範圍查詢,那麼其右邊的全部列將沒法使用索引優化,因此儘可能將範圍條件放在右邊或者使用多個等值條件來代替範圍查詢
  • ORDER BY 中的排序的列若是建了索引,則有可能使用索引進行排序,進行優化性能
  • 只有當索引的列和 ORDER BY 子句的順序徹底一致且全部列的排序方向一致時才能使用索引作排序
  • 哈希索引對於等值查詢的性能提高很是高,可是哈希索引沒法用來排序,也不支持部分索引列匹配查找
  • 在使用索引時對應的索引列必須獨立,不能是表達式的一部分也不能是函數的參數,不然不能使用索引:
-- 雖然 id 上創建了索引,可是沒法使用索引優化
select id from user where id + 1 =5;
複製代碼
  • 當服務器出現多個列作 AND 操做查詢時,一般須要建了一個多列索引,而不是多個獨立的單列索引
  • 當不須要考慮排序和分組時,將選擇項最高的列放在前面一般是最好的,由於能夠很快的過濾出須要的行
  • 若是索引包含了須要查詢的全部字段值,那麼就是可使用覆蓋索引查詢,只須要讀取索引,極大地減小了數據訪問量,在 EXPLAIN 分析的 Extra 字段中能夠看到 「Using index」 信息
  • 查詢時儘可能不要返回多餘的列,第一能夠減小網絡流量,第二增長使用覆蓋索引的可能性
  • 若是關聯多張表時,只有當 ORDER BY 子句引用的字段所有是第一張表時才能使用索引排序
  • 默認類型轉換不只增長開銷,還會使索引失效,好比 col 是 VCHAR 類型,那麼 where col = '10' 會使用索引,而 where col = 10 不會使用索引
  • 不要建立冗餘的索引,Mysql 不只須要單獨維護索引列,而且在優化器查詢時也須要逐個索引進行過濾,會影響性能

下面是建立冗餘索引的幾個例子:算法

- 建立了索引(A,B)再建立索引(A),那後者即是冗餘索引
- 將一根索引擴展爲(A,ID),其中 ID 是主鍵,對於 InnoDB 來講主鍵已經包含在二級索引中了,因此這也是冗餘的
複製代碼
  • 有一些索引可能服務器永遠都不會用到,建議考慮刪除,在 percona 版本或 marida 中能夠經過 information_schea.index_statistics 查看獲得索引的使用狀況,在官方版本中可使用 performance_schema.table_io_waits_summary_by_index_usage 查看索引使用狀況:
- 能夠查到使用最多或者使用最少的表和索引
- 能夠查到從未使用過的索引,考慮刪除之
- 能夠查到線程的使用狀況等等
複製代碼

事務優化

  • 儘可能不要在事務中混合使用存儲引擎,若是有些表支持事務,有些表不支持事務,回滾時會致使數據不一致問題
  • 在應用層應該檢查在事務中是否存在 RPC 調用、HTTP 調用、消息隊列、緩存、循環查詢等耗時的操做,這個操做應該儘可能移到事務以外,由於這些操做會增長事務的處理時間,使 sql 查詢不穩定,理想的狀況是事務內只處理數據庫操做;

其它查詢優化

  • 一個大的 DELETE 或者 UPDATE 查詢極可能會一次性鎖住不少數據,佔滿整個事務日誌,阻塞其它小的重要的查詢,若是有可能能夠把大的查詢拆分紅多個小的查詢。
  • 關聯查詢分解:
- 讓單表查詢的緩存效率更高
- 拆分後用 IN() 代替關聯查詢,可讓 Mysql 按照 ID 順序去查找
- 能夠將數據分佈到不一樣的 Mysql 服務器上
複製代碼
  • 使用 IN 加子查詢性能一般都會很低,因此建議使用 EXISTS 等效的查詢來獲取更好的效率
  • UNION 操做會比 UNION ALL 操做耗時,由於 UNION 操做在合併之後,還要做去重排序操做,除非必須使用 UNION 查詢,不然就使用 UNION ALL 查詢
  • 能寫在 WHERE 條件中判斷不要寫在 HAVING 子句中,由於 GROUP BY 會對數據進行排序,若是事先排除掉一些數據,會減小排序量,還有就是聚合後的視圖可能索引條件已經丟失
  • IS NULL 或者 IS NOT NULL 查詢會使索引失效
  • 當覺得當前查詢只有一行數據時使用可使用 LIMIT 1,這樣檢索到一條數據後,就中止搜索了
  • HAVING 子句和 GROUP BY 子句一塊兒使用時比先 GROUP BY 成中間表再執行 WHERE 要快
  • GROUP BY 子句會自動對分組的列進行排序,若是不但願進行排序可使用 ORDER BY NULL
  • 儘量將 GROUP BY WITH ROLLUP 放到應用程序去完成,由於 Mysql 作超級聚合每每性能不佳
  • 優化策略在 UNION 查詢中無法很好的使用,通常須要將 WHERE,ORDER BY,LIMIT 子句下推到各個子查詢中
  • 優化 COUNT() 查詢:
- 若是是統計結果集的大小,請使用 COUNT(*),使用 COUNT(cloumn) 有可能某個列存在 NULL 致使統計不許確,排除 NULL 計算也是要成本的
- 對於 MyIsam 存儲引擎,若是不帶任何 WHERE 條件的狀況下, COUNT(*) 不須要計算,直接經過存儲引擎特性得到
複製代碼
  • LIMIT 分頁優化
-- 分頁時對於偏移量特別大的狀況下,查詢全部列分頁將很是耗時,可使用「延遲關聯」的方式,其中一個查詢中儘量的使用索引覆蓋掃描方式 LIMIT 查詢出主鍵 ID,而後再和原表作一次關聯返回須要的列:
-- 優化前
select * from user order by id limit 1000, 5;
-- 優化後
select user.* from user join (select id from user order by id limit 1000, 5) new_user on new_user.id = user.id;

-- 若是在一個位置上預先計算出了邊界,能夠將 limit 查詢轉換爲已知位置的查詢進行優化
select * from user where id between 1000 and 1005 order by id 
複製代碼

使用查詢提示進行優化

若是對優化器的執行計劃不滿意可使用優化器的幾個提示來控制最終的執行計劃:sql

HIGH_PRIORITY 和 LOW_PRIORITY

HIGH_PRIORITY 和 LOW_PRIORITY 對於使用表鎖的存儲引擎有效,HIGH_PRIORITY 會將當前查詢插入到全部處於表鎖等待的 SQL 隊列前面,而 LOW_PRIORITY 會將當前查詢放在全部等待表鎖的 SQL 隊列隊尾,只要隊列中還有須要訪問同一張表的 SQL, 它就被處於等待狀態。數據庫

DELAYED

該提示對 INSERT 和 REPLACE 有效,使用該提示後會當即返回給客戶端,而後將插入的行放入緩存區,等待表空閒時批量寫入數據。緩存

該操做致使 LAST_INSERT_ID() 函數沒法正常工做。安全

對於一些數據記錄,即便插入失敗也不影響服務正常運行,可使用該操做,及時響應客戶端,加快響應速度。bash

STRAIGHT_JOIN

讓全部查詢中的的表按照語句中出現的順序進行關聯,不須要 Mysql 優化器去從新選擇關聯順序,若是能確保本身寫的關聯順序性能比較好的狀況下能夠選擇該提示,減小 Mysql 優化器自己選擇分析的時間。服務器

SQL_SMALL_RESULT 和 SQL_BIG_RESULT

這兩個提示針對 select 操做,告訴優化器對 group by 或 distinct 如何使用臨時表及排序,若是 SQL_SMALL_RESULT 表示結果集很小,使用內存排序,若是是 SQL_BIG_RESULT 表示結果集很大,使用磁盤臨時表排序。網絡

SQL_CACHE 和 SQL_NO_CACHE

這個提示告訴 Mysql 結果集是否要緩存在查詢緩存中數據結構

SQL_CALC_FOUND_ROWS

FOUND_ROWS 這個函數通常狀況下只會返回上一次查詢的數據集大小,可是若是加了 SQL_CALC_FOUND_ROWS 提示,那麼將返回不帶 limit 狀況下整個數據集大小,這個參數對於分頁有必定的用處,不須要屢次查詢。

FOR_UPDATE 和 LOCK IN SHARE MODE

該提示只對支持行級鎖的存儲引擎生效,該提示會對查詢中符合條件的數據加鎖

這兩個提示會讓 InnoDB 覆蓋索引優化失效,由於 InnoDB 須要訪問主鍵中的版本信息。

USE INDEX 和 IGNORE INDEX 及 FORCE INDEX

告訴優化器是否使用某個索引

合理使用分區表

  • 分區表數據更容易維護,想刪除大量數據能夠直接使用清除某個分區的方式,而且能夠獨立備份和恢復某個分區
  • 分區表的數據能夠分佈到多個物理設備上,有效的利用硬件設備
  • 若是分區列有 NULL 值,可能使分區過濾無效,由於 NULL 值會被存儲在第一個分區中
  • 避免創建與分區列不匹配的索引,由於這樣根據索引查詢會使分區沒法區分
  • 在查找訪問分區時,Mysql 須要打開並鎖住全部的底層表,對於簡單的查詢來講這個消耗仍是有點高,可使用批量操做減小開銷次數
  • 全部分區都必須使用相同的存儲引擎,分區中可使用的函數和表達式也有必定的限制
  • Mysql 只能使用分區函數列自己查詢時纔可使用分區過濾,不能將分區列放入表達式,此時沒法找到對應分區進行過濾

合理使用視圖/外鍵/觸發器

  • 建立視圖有兩種算法:臨時表算法和合並算法,若是可能儘可能使用合併算法,使用合併算法時 Mysql 會將視圖與基於視圖的查詢語句進行合併而後優化器基於此進行優化
  • 經過 explain 解析字段 select_type 判斷視圖使用臨時表算法仍是合併算法,在建立查詢時能夠指定具體使用什麼算法,
  • 若是隻是使用外鍵作約束,那麼一般在應用程序裏實現會更好,外鍵會帶來很大的額外開銷
  • 觸發器容易掩蓋背後的工做,並且問題比較難以排查,可能致使死鎖,儘可能不要使用觸發器

合理使用綁定變量

  • 使用綁定變量,Mysql 服務器只須要解析一次 SQL 語句,而且會緩存一部分執行計劃
  • 使用綁定變量每次僅僅發送的參數,而不是整個查詢語句,減小網絡開銷
  • 綁定變量也相對安全,不須要處理轉義,大大減小 SQL 注入和攻擊的風險
  • 綁定變量是會話級別的,不一樣鏈接之間不能共用

合理使用查詢緩存

  • 若是表發生變化,對應的查詢緩存則會失效
  • 查詢緩存是否命中與自己查詢 SQL,查詢的數據庫,客戶端協議的版本有關係
  • 查詢中包含自定義函數,存儲函數,用戶變量,臨時表,Mysql 庫中的系統表都不會設置緩存,也不會命中緩存
  • 只有整個事務提交後,相關的查詢結果纔會被緩存
  • 查詢緩存對於複雜計算,耗時比較長的查詢有很大優化效果,
  • 對於簡單的查詢,由於查詢緩存的預判檢查也自己比較耗時,再加上數據變化比較快時,相反會下降性能
  • 建議查詢時使用 SQL_CACHE 和 SQL_NO_CACHE 來進行選擇性的使用查詢緩存
  • 對於 InnoDB 若是表上有任何鎖,那麼任何查詢都沒法從緩存中讀取與這個表相關的緩存結果
  • 如何優化查詢緩存:
- 用多個小表代替一個大表,可讓緩存失效在一個更細的粒度上進行
- 批量寫入時只作一次緩存失效,因此比單條寫入更好
- 若是沒法在數據庫或者表級別控制查詢緩存,則可使用 SQL_CACHE 和 SQL_NO_CACHE 來控制單個 select 語句是否進行緩存,而且能夠修改會話級別的 query_cache_type 來控制查詢緩存
- 對於寫密集型的應用來講,關閉查詢緩存對性能會更好
複製代碼

合理使用 Mysql 服務器配置

  • mysql 的配置文件通常在 /etc/my.cnf 或者 /etc/mysql/my.cnf
  • 任何打算長期保存的配置都應該經過配置文件保存,不該該在命令行裏生效,以防下次啓動失效
  • DEFAULT 是一個特殊值能夠經過 SET 設置給變量:這個值會把會話級變量設置爲全局變量,會把全局變量設置爲編譯器內置的默認值
  • mysql 主要的幾個環境變量配置說明:
datadir=    /var/lib/mysql                  # 數據的存儲位置

user=       mysql                           # 執行 mysql 用戶運行 mysql 實例,要保證該用戶存在

port=       3306                            # mysql 實例的端口號

socket:    =/var/lib/mysql/mysql.sock      # socket 文件存儲位置,用於 TCP/IP 套接字鏈接數據庫

pid_file    = /var/lib/mysql/mysql.pid      # mysql 進程 id

default_storage_engine        = InnoDB      # 默認的存儲引擎

innodb                        = FORCE       # 只有在 Innodb 存儲引擎正常啓動時,服務器才能正常啓動,通常建議設置爲 FORCE,保證能夠正確使用 InnoDB 存儲引擎

innodb_buffer_pool_size       = <value>     # InnoDB 存儲引擎可使用的緩存大小

innodb_log_file_size          = <value>     # 設置重作日誌大小,過小寫入日誌須要頻繁的刷新磁盤,使寫入變慢,太大奔潰恢復時間變慢,要合理設置

innodb_thread_concurrency     = 0           # 它能夠限制一次性有多少線程進入內核,0 表示不限制。通常建議設置爲:CPU 數量 * 磁盤數量 * 2

innodb_thread_sleep_delay     = 10000       # 爲了減小由於操做系統調度引發的上下文切換,線程第一次沒法進入內核會休眠 innodb_thread_sleep_delay 秒之後再嘗試
                                            # 若是再次沒法進入內核,則放入線程等待隊列,讓操做系統來處理
                                            
innodb_file_per_table         = 1           # 是否讓每一張表使用一個獨立文件存儲,使得刪除表變的簡單,而且容易分散到不一樣的磁盤上,可是會致使空間的浪費

innodb_flush_method           = 0_DIRECT    # 控制 InnoDB 如何和文件系統相互做用,控制將數據刷新到磁盤的方式,要不要使用磁盤緩存等

key_buffer_size               = <value>     # MyISAM 存儲引擎分配的鍵緩存大小,該值對使用 MyISAM 存儲引擎的數據庫很是重要
                                            # 即便是使用 InnoDB 存儲引擎也應該分配必定空間(32M),由於 Mysql 中一些系統表會使用 MyISAM 存儲引擎
                                            # Group by 建立臨時表時也可能使用 MyISAM 存儲引擎
                                            
sort_buffer_size              = <value>     # 該參數會在查詢使用內存排序時分配內存,一旦須要排序就會指定這麼大的內存,無論是否須要這麼大的內存 
                                            # 通常建議把 sort_buffer_size 修改的小一點,若是某個查詢確實須要很大內存排序,能夠在會話級臨時調大該值
                                            
log_error=/var/lib/mysql/mysql-error.log    # 錯誤日誌存放位置

slow_query_log=/var/lib/mysql/mysql-show.log# 慢查詢日誌存放位置

tmp_table_size/max_heap_table_size = 32M    # 這兩個變量用於控制使用內存臨時表(Memory存儲引擎)的大小,若是超過這個值,將使用磁盤臨時表(MyISAM存儲引擎)
                                            # 經過 show status 觀察 Created_tmp_disk_tables 和 create_tmp_tables 的變化來調整這兩個參數
                                            
query_cache_type              = 0           # 控制查詢緩存功能的開啓和關閉,0 表示關閉,1 表示開啓,2 表示只有 select 中明確指定 SQL_CACHE 才緩存

query_cache_size              = 0           # 設置查詢緩存的大小

max_connections               = <value>     # 最大鏈接數,默認是 100,每每過小,若是過小會報太多鏈接被拒絕的錯誤,觀察 Max_used_connections 狀態變量來設置該參數

thread_cache_size             = <value>     # 指定 Mysql 能夠保存在緩存中的線程數,通常經過觀察 Thread_connected 變量的大小來調整該值的大小

table_cache_size              = 1000        # 設置表緩存大小,設置足夠的大小以免老是須要從新打開並從新解析表定義

open_files_limit              = 65535       # 這個參數能夠儘可能設置大,由於打開句柄的開銷很小,不然會出現「too many open files」

expire_logs_days              = 10          # 服務器在指定的天數後清理二進制日誌

max_connect_errors            = 100         # 允許某個應用最大錯誤次數,若是超過該值,將被加入黑名單,除非刷新主機緩存
複製代碼
  • 幾個 timeout 相關參數說明:
- connect_timeout

在獲取鏈接階段(authenticate)起做用,獲取 MySQL 鏈接是屢次握手的結果,除了用戶名和密碼的匹配校驗外,還有 IP->HOST->DNS->IP 驗證,任何一步均可能由於網絡問題致使線程阻塞。
爲了防止線程浪費在沒必要要的校驗等待上,超過 connect_timeout 的鏈接請求將會被拒絕,默認值 10 秒。

- interactive_timeout 和 wait_timeout

在鏈接空閒階段(sleep)起做用,即便沒有網絡問題,也不能容許客戶端一直佔用鏈接。
對於保持 sleep 狀態超過了 wait_timeout(或 interactive_timeout,取決於 client_interactive 標誌)的客戶端,MySQL 會主動斷開鏈接,默認值是 8 小時。

- net_read_timeout 和 net_write_timeout

則是在鏈接繁忙階段(query)起做用,即便鏈接沒有處於 sleep 狀態,即客戶端忙於計算或者存儲數據,MySQL 也選擇了有條件的等待。
在數據包的分發過程當中,客戶端可能來不及響應(發送、接收、或者處理數據包太慢)。
爲了保證鏈接不被浪費在無盡的等待中,MySQL 也會選擇有條件(net_read_timeout和net_write_timeout)地主動斷開鏈接。默認是 30 秒。

- innodb_lock_wait_timeout

innodb 使用這個參數可以有效避免在資源有限的狀況下產生太多的鎖等待,指的是事務等待獲取資源時等待的最長時間,超過這個時間還未分配到資源則會返回應用失敗。
參數的時間單位是秒,最小可設置爲1s(通常不會設置得這麼小),最大可設置1073741824秒(34年),默認安裝時這個值是 50 s。
超過這個時間會報 ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction。

- innodb_rollback_on_timeout

默認狀況下 innodb_lock_wait_timeout 超時後只是超時的 sql 執行失敗,整個事務並不回滾,也不作提交。
如須要事務在超時的時候回滾,則須要設置 innodb_rollback_on_timeout=ON,該參數默認爲 OFF。

- lock_wait_timeout

和 innodb_lock_wait_timeout 的區別是前者是 Innodb 的 DML 操做的行級鎖的等待時間,後面是數據結構 DDL 操做的鎖的等待時間。

- innodb_flush_log_at_timeout

參數 innodb_flush_log_at_trx_commit = 1 時,此超時參數不起做用。當 innodb_flush_log_at_trx_commit=0/2 時才起做用。
表示每 innodb_flush_log_at_timeout 秒進行一次的頻率刷新 redo log(在 5.6.6 版本以前是固定每秒一次刷新 redo log,5.6.6 版本以後刷新頻率能夠經過這個參數設置,固然,這個參數自己默認值也是 1S)
複製代碼
相關文章
相關標籤/搜索