1. 程式人生 > >22 mysql有那些”飲鴆止渴”提高效能的方法?

22 mysql有那些”飲鴆止渴”提高效能的方法?

22 mysql有那些”飲鴆止渴”提高效能的方法?

正常的短連線模式是連線到資料庫後,執行很少的SQL語句就斷開,下次需要的時候再重新連線。如果使用的是短連線,在業務高峰期的時候,就可能出現連線數突然暴漲的情況。

Mysql建立連線的過程,成本是很高的,除了正常的網路連線的3次握手外,還需要做登入許可權判斷和獲得這個連線的資料讀寫許可權。

在資料庫壓力比較小的時候,這些額外的成本並不明顯。

但是,短連線模型存在一個風險,就是一旦資料處理得慢一些,連線數就會暴漲。max_connections引數,用來控制一個mysql例項同時存在的連線數的上限,

超過這個值,系統就會拒絕接下來的連線請求,並報錯

”Too many connections ”。對於被拒絕連線的請求來說,從業務角度看就是資料庫不可用

在機器負載比較高的時候,處理現有請求的時間變長,每個連線保持的時間也更長,這時,再有新建連線的話,就可能會超過max_connections的限制。

碰到這種情況,一個比較自然的想法,就是調高max_connections的值,這樣做是有風險的,因為設計max_connections的這個引數的目的是想保護MySQL

如果改得太大,讓更多的連線都可以進來,那麼系統的負載可能會進一步加大,大量的資源耗費在許可權驗證等邏輯上,結果可能適得其反,已經連線的執行緒拿不到cpu資源去執行業務的

SQL請求。

這種情況下,建議兩種方法:

第一種:先處理掉那些佔著連線但是不工作的執行緒

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中,會引發效能問題的慢查詢,大體有一下三種可能:

  1. 索引沒有設計好
  2. Sql語句沒有寫好
  3. 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的方案,比如ptoak等工具

導致慢查詢的第二種可能,語句沒寫好

比如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 三個行鎖。