22 mysql有那些”飲鴆止渴”提高效能的方法?
22 mysql有那些”飲鴆止渴”提高效能的方法?
正常的短連線模式是連線到資料庫後,執行很少的SQL語句就斷開,下次需要的時候再重新連線。如果使用的是短連線,在業務高峰期的時候,就可能出現連線數突然暴漲的情況。
Mysql建立連線的過程,成本是很高的,除了正常的網路連線的3次握手外,還需要做登入許可權判斷和獲得這個連線的資料讀寫許可權。
在資料庫壓力比較小的時候,這些額外的成本並不明顯。
但是,短連線模型存在一個風險,就是一旦資料處理得慢一些,連線數就會暴漲。max_connections引數,用來控制一個mysql例項同時存在的連線數的上限,
超過這個值,系統就會拒絕接下來的連線請求,並報錯
在機器負載比較高的時候,處理現有請求的時間變長,每個連線保持的時間也更長,這時,再有新建連線的話,就可能會超過max_connections的限制。
碰到這種情況,一個比較自然的想法,就是調高max_connections的值,這樣做是有風險的,因為設計max_connections的這個引數的目的是想保護MySQL,
如果改得太大,讓更多的連線都可以進來,那麼系統的負載可能會進一步加大,大量的資源耗費在許可權驗證等邏輯上,結果可能適得其反,已經連線的執行緒拿不到cpu資源去執行業務的
這種情況下,建議兩種方法:
第一種:先處理掉那些佔著連線但是不工作的執行緒
max_connections的計算,不是看誰在running,是隻要連著就佔用一個計數位置。對於那些不需要保持的連線,可以通過kill connection主動踢掉。
這個行為跟事先設定wait_timeout的效果是一樣的。設定wait_timeout引數表示的是,一個執行緒空閒wait_timeout這麼多秒之後,就會被MySQL直接斷開連線。
但需要注意,在show processlist的結果裡,踢掉顯示為sleep的執行緒,可能是有損的,
|
SESSION A |
SESSION B |
SESSION C |
T |
begin; insert into t values(1,1); |
select *from t where id =1; |
|
T+30S |
|
|
show processlist; |
在上面的例子中,如果斷開session A的連線,因為這時候還沒有提交,所以mysql只能按照回滾事務來處理,而斷開session B的連線,
就沒什麼大的影響。所以,如果按照優先順序來說,應該處理像session B這樣的事務外空閒的session。
怎麼判斷事務外空閒的呢,執行show processlist
其中,處於sleep狀態的會話,要看具體的事務狀態的話,可以檢視
([email protected]:3306) [(none)]> select * from information_schema.innodb_trx\G;
因此,如果是連線數過多,你可以優先斷開事務外空閒太久的連線,如果這樣還不夠,再考慮斷開事務內空閒太久的連線。
從伺服器端斷開連線使用的是kill connection +id的命令,一個客戶端處於sleep狀態時,它的連線被server主動斷開後,
這個客戶端並不會馬上知道,直到客戶端發起下一個請求的時候,才會收到這樣的錯誤”ERROR 2013 (HY000): Lost connection to MySQL server during query”。
從資料庫端主動斷開連線可能是有損的,尤其是有的應用端收到這個錯誤後,不重新連線,而直接用這個已經不能用的控制代碼重試查詢,這會導致從應用端上看上去,”mysql一直沒恢復”。
所以,如果是一個支援業務的DBA,不要假設所有的應用程式碼都會被正確處理,即使只是一個斷開連線的操作,也要確保通知到業務開發團隊。
第二種方法:減少連線過程中的消耗
有的業務程式碼會在短時間內先大量申請資料連線做備用,如果現在資料庫確認是被連線行為打掛了,那麼一種可能的做法,是讓資料庫跳過許可權認證階段。
方法,重啟資料庫,並使用引數-skip-grant-tables引數啟動,這樣,這個mysql會跳過所有的許可權認證階段,包括連線過程和語句執行過程在內。
但是,這種方法是符合”飲鴆止渴”,風險極高,是特別不建議的方案,尤其是庫外網可以訪問的話,就更不能這麼做。
在mysql 8.0的版本里,開啟引數-skip-grant-tables引數,mysql會預設把-skip-networking引數開啟,表示這個時候資料庫只能被本地的客戶端連線,可見,對安全的問題的重視。
慢查詢效能問題
在mysql中,會引發效能問題的慢查詢,大體有一下三種可能:
- 索引沒有設計好
- Sql語句沒有寫好
- Mysql選錯了索引
分析這三種可能,以及應對的方案
導致慢查詢的第一種可能,索引沒有設計好
這種場景一般就是通過緊急建立索引來解決。Mysql5.6版本以後,建立索引支援online ddl,對於那種高峰期資料庫已經被這個語句打掛了情況,最高效的做法就是直接執行alter table語句。
比較理想的是能夠在備庫先執行,假設現在一主一備,主庫A、備庫B,
1 在備庫B上執行set sql_log_bin=off,不寫binlog,然後執行alter table 加上索引
2 執行主備切換
3 在新備庫A上執行set sql_log_bin=off,不寫binlog,然後alter 加上所以
這是一個古老的ddl方案,平時在做變更的時候,可以考慮類似的go-ost的方案,比如pt、oak等工具
導致慢查詢的第二種可能,語句沒寫好
比如select * from t where id +1=9999;
這種,我們可以通過改寫sql來處理,mysql5.7.6之後提供了query_rewrite功能,可以把輸入的一種語句改寫成為另外一種模式。
這裡可以找到query_rewrite的用法
([email protected]:3306) [(none)]> show variables like '%plug%';
+---------------+--------------------------+
| Variable_name | Value |
+---------------+--------------------------+
| plugin_dir | /usr/lib64/mysql/plugin/ |
+---------------+--------------------------+
--create table
--create procedure
--mysql -u root -p < install_rewriter.sql
mysql> insert into query_rewrite.rewrite_rules(pattern, replacement, pattern_database) values ("select * from t where id + 1 = ?", "select * from t where id = ? - 1", "db1");
call query_rewrite.flush_rewrite_rules();
這裡呼叫過程,是讓插入的規則生效,也就是查詢重新
導致慢查詢的第三種可能,就是mysql使用錯誤的索引
這時候的應急方案就是給這個語句加上force index
同樣的,使用查詢重寫功能,給原來的語句加上for index,也可以解決這個問題
預發現問題
1上線前,在測試環境,把慢查詢(slow log)開啟,設定long_query_time=0,確保每個語句都會記錄慢查詢日誌
2 在測試表裡插入模擬線上的資料,做一遍迴歸測試
3 觀察慢查詢日誌裡每類語句的輸出,特別留意Rows_examined欄位是否與預期的一致
如果新增的語句sql不多,手動跑一下,而如果是新專案的話,或者修改原有專案表結構設計,全量回歸測試都是必要的,這時候,可以用工具來檢查所有的sql的返回結果--pt-query-digest(https://www.percona.com/doc/percona-toolkit/3.0/pt-query-digest.html)
QPS突增問題
有時候由於業務突然出現高峰,或者應用程式出現bug,導致某一個語句的QPS突然暴漲,也可能導致mysql壓力過大,影響服務。
之前碰到過一類情況,是由一個新功能的bug導致的,當然,最理想的情況讓業務把這個功能下掉,服務就會自然恢復。
而下掉一個功能,如果從資料庫端處理的話,對應於不同的背景,有不同的方法可用。
1 一種是由全新業務的bug導致的,假設你的db運維是比較規範的,也就是說白名單是一個個加的,這種情況下,如果能夠確定業務方下掉這個功能,只是時間上沒那麼快,就可以從資料庫端直接把白名單去掉
2 如果這個新功能使用的是單獨的資料庫使用者,可以用管理員賬號把這個賬號刪掉,然後斷開連線,這樣,這個新功能的連線不成功,由它引發的qps就會變成0
3 如果這個新增功能跟主體功能部署在一起,那麼我們只能通過處理語句來限制,這是,可以使用上面提到的查詢重寫的功能,把壓力最大的sql直接重寫為select 1.返回
當然,這個操作的風險很高,需要特別的細緻,它可能存在兩個副作用
1 如果別的功能裡面也有用到這個sql語句模板,會有誤傷
2 很多業務並不是靠這一個語句就完成邏輯,所以如果單獨把這個語句以select 1 返回結果的話,可能會導致後面的業務邏輯的一起失敗
所以,方案3是用於止血的,跟前面提到的去掉使用者許可權驗證一樣,應該是方案裡優先順序最低的一個。
小結
以業務高峰期的效能問題為背景,介紹了一些緊急的處理手段。
這些手段中,既包括了粗暴的拒絕連線和斷開連線,也有通過查詢重寫來繞過一些坑,既有臨時的高危方案,又有相對安全的預案。
在實際開發中,我們要儘量避免一些低效的做法,比如大量的使用短連線,同時,做業務開發,要知道,連線異常斷開是常有的事,程式碼裡要有正確的重連並重試的機制。
DBA雖然可以通過語句重寫來暫時處理問題,但這本身是一個風險極高的操作,做好SQL審計以減少需要這樣操作的機會。
上起問題解答
SESSION A |
SESSION B |
SESSION C |
begin; select * from t20 where c>=15 and c<=20 order by c desc for update; |
|
|
|
insert into t20 values(11,11,11);(blocked) |
|
|
|
insert into t20 values(6,6,6);(blocked) |
SESSION B阻塞已經不用分析,來分析SESSION C為什麼會阻塞
1 session A加鎖分析,由於是order by c desc,第一個要定位的是索引c上的”最右邊的”c=20的行,所以會加上間隙鎖(20,25)和next-key lock(15,20]
2 在索引c上向左遍歷,要掃描到c=10才停下來,所以next-key lock會加到(5,10],這個正是阻塞session c的原因
3 在掃描過程中,c+20,c=15,c=10這三個值都存在,由於是select * ,所以會在主鍵id上加3個行鎖。
因此SESSION A的加鎖返回
1 索引c上(5,25)
2 主鍵索引上c=10,c=15,c=20 三個行鎖。