一. 大綱
在日常的數據庫運維管理中,數據庫腳本是排查問題必不可少的利器,好的運維腳本可以讓我們快速排查出問題所在,並在最短時間內解決問題。
但是在實際項目中,很多項目組人員面對數據庫方面的突發問題,特別是SQL性能方面的問題,都無從下手,反饋給DBA的大多都是系統卡頓
或數據庫存在問題
等內容,而DBA更想要知道的其實是一些更深層次的信息,比如當前數據庫的SQL運行情況
,事務狀態
以及是否存在阻塞
等有效的診斷信息
,以此來定位是否是數據庫層面的問題。
有時候也許項目組反饋的一個截圖就可以讓DBA快速定位到問題原因,而省去遠程排查的時間開銷,大大提高工作效率,這中間缺的其實就是一個合適的腳本,一個便於項目組獨立排查問題與節省溝通成本的工具。
本篇就來介紹一下三大數據庫中一些非常實用的運維腳本,希望可以幫助大家更好的排查解決數據庫方面的問題,也希望可以提高下項目組反饋數據庫問題時的內容深度。
PS:本篇介紹的腳本主要用於SQL層面問題
的實時排查
,如頁面卡頓
,CPU爆滿
,行鎖超時
,連接池或最大連接數爆滿
等現象,一切你懷疑是因為SQL執行效率低而引發的性能問題,都可以通過對應的腳本進行排查,確認是否是SQL慢引發的問題,亦或是找到具體哪條SQL惹的禍。通常這類性能問題爆發時,通過腳本去進行實時排查
比事后通過慢日志分析
更容易精准定位問題
。
示例(1)
:某個頁面每次打開都很慢,那么你可以刷新下頁面,再通過腳本監控下SQL運行情況,如果發現有SQL運行很慢或者有阻塞,與開發確認后確實是這個頁面上執行的SQL,那么問題鎖定,就是SQL問題導致頁面打開慢;如果未發現有執行很慢的SQL或是阻塞,那么基本可以證明頁面打開慢和數據庫沒關系,需要排查其他組件是否存在問題。
示例(2)
:系統全面卡死,許多項目人員非常喜歡直接重啟數據庫或者應用程序
來臨時解決問題
,事后再讓DBA去查問題根因。雖然這種方式確實比較有效,但是對於DBA來說,就像第一殺人現場被破壞后再讓你破案,比較困難。所以我想着是,大家可以在重啟前執行下相關數據庫腳本,盡量保留下當時的快照數據,方便后續DBA排查問題原因。很多人應該經歷過事后客戶領導要排查原因,但是又找不出的痛楚吧
。
二. MySQL篇
2.1. 事務狀態腳本
腳本介紹:通過該腳本,可以看到當前MySQL中有哪些打開的事務,事務打開了多久,當前狀態是什么,是在執行SQL,還是處於掛起等。
強烈推薦,這個腳本是我經常使用的一個MySQL腳本,排查問題必備。
腳本正常輸出如下,事務都比較短(duration列值較小
),當然某些數據交換作業會跑比較大的事務,所以要根據具體SQL判斷:
輸出字段解釋:
-
id:會話ID,可以使用
kill + id
結束該會話。 -
connection:連接信息,
user@ip:db
。 -
trx_state:事務執行狀態,有RUNNING、LOCK WAIT、ROLLING BACK、COMMITTING四種狀態。
-
trx_started:事務打開時間。
-
duration
:事務執行時長,單位秒。 -
trx_query
:事務當前運行SQL,如果為空,說明事務處於掛起狀態
,可以結合thd_last_query
定位代碼位置。 -
state
:線程會話當前狀態。 -
thd_rows_examined:線程掃描行數。
-
trx_rows_locked:事務持有行鎖數。
-
trx_rows_modified:事務影響行數。
-
trx_isolation_level:事務隔離級別。
-
thd_last_query
:線程包含的歷史SQL(按照執行時間倒序
且默認最多存在10條SQL),如果事務處於掛起狀態
,則可以根據歷史執行SQL找出代碼位置。 -
thread_id:線程id。
注意點:此腳本可以知道MySQL中所有活躍事務的當前狀態。但是需要注意,如果事務執行的第一條SQL就被全局讀鎖或者元數據鎖卡住,則無法從該腳本中看到,即INNODB_TRX表看不到這個未真正開始的事務
;如果第二條或后面的SQL執行被卡主,則可以看到。
問題樣例(1)
:MySQL服務器cpu爆滿,腳本輸出如下,duration比較大,大量事務卡在同一條SQL執行上,分析該SQL發現缺少索引
,加上索引后cpu正常。
問題樣例(2)
:業務系統訪問超時,腳本輸出如下,很多事務卡在commit上,登陸MySQL服務器檢查后發現磁盤爆滿
所致。
問題樣例(3)
:事務掛起問題,事務掛起容易導致鎖等待,掛起的原因有很多,最常見的就是接口問題。模擬問題環境腳本輸出如下,可以看到事務運行了非常久,但是trx_query列為空
,說明會話此時並不在執行SQL,我們可以通過thd_last_quert
看下事務會話之前運行過哪些SQL,再根據歷史SQL去代碼中定位原因。
2.2. 表鎖等待腳本
腳本介紹:用於查看當前MySQL中是否存在
表鎖等待
,這里表鎖包括:全局讀鎖,表級讀寫鎖,元數據鎖,這些鎖通常出現在備份還原或DDL操作中。
當我們進行DDL操作
的時候,有可能因為無法拿到表的元數據鎖(MDL)
出現等待,導致后續表上的操作都被卡住,這時候可以通過這個腳本找到可疑事務,臨時KILL解決。
輸出字段解釋:
-
kill_sql
:拼接好的kill命令。 -
connection:線程連接信息。
-
command:線程執行命令。
-
time
:線程處於當前狀態的時間,單位秒。 -
state
:線程狀態,常見的有:-
Waiting for table flush
等待表關閉。 -
Waiting for global read lock
等待全局讀鎖。 -
Waiting for table metadata lock
等待表元數據鎖。
-
-
info
:線程當前執行的SQL。 -
trx_started:事務打開時間。
如果MySQL中不存在表鎖等待,那么輸出結果為空。如果存在輸出,則說明存在表鎖等待,可以通過kill命令將可疑的事務終止來解決,例如:
這個例子輸出可以明顯的看出是292事務
的問題,但是絕大多數時候腳本輸出都不會如此明顯,我個人的建議是先把state狀態不是Waiting for的長事務kill掉
,如果kill后還是存在表鎖,則可以考慮將所有的事務都kill再觀察。
2.3. 行鎖等待腳本
腳本介紹:用於查看當前MySQL中是否存在
行鎖等待
,個人用的不多,因為MySQL中行鎖等待超過默認5s
就會超時報錯,因此很少能正好查詢監控到,更多的是放到后台監控腳本中循環執行,而非手動執行,詳見如何有效排查解決MySQL行鎖等待超時問題。
輸出字段解釋:
-
waiting_id:等待線程id。
-
blocking_id:阻塞線程id。
-
duration
:行鎖等待時間。 -
state
:阻塞線程狀態,sleep代表事務掛起。 -
waiting_query
:等待SQL。 -
blocking_current_query
:阻塞SQL,為空代表事務掛起。 -
thd_last_query
:阻塞線程歷史SQL,事務掛起時可以通過該列排查。
2.4. show processlist
腳本介紹:嚴格來說,這並非是腳本,而是MySQL提供的管理命令,可以看到當前所有會話的連接信息,狀態,SQL等,非常方便,但是輸出信息不是很詳細。
輸出如下,可以較為清晰的展示MySQL的運行情況,但是遇到事務相關等待問題還是上述的腳本較為方便,當Info列包含的SQL較長時,show processlist命令可能無法完整展示,這時候可以通過show full processlist
完整展示SQL。
輸出字段解釋:
-
Id:會話ID。
-
User:用戶。
-
Host:客戶端IP。
-
db:數據庫。
-
Command
:線程執行命令。 -
Time
:線程處於當前狀態的時間,單位秒。 -
State
:線程的狀態,和 Command 對應。 -
Info
:線程執行的SQL。
注意點:其中Time、Info列
比較重要,Info列
展示當前線程運行SQL
,如果為空,則說明當前線程為sleep狀態,而Time列
則代表當前狀態維持的時長(s)
,如果Info有具體的SQL,則代表該SQL運行時長,如果Info為空,則代表線程空閑等待時長,線程每次執行新的SQL,Time都會重新計時
。
很多同事經常會問,我的MySQL中出現很多sleep狀態的線程且Time很大是有問題嘛?其實是很正常的,這些sleep線程其實是因為druid連接池的原因,所以一直並未真正關閉,處於空閑等待。
三. Oracle篇
3.1. 長事務腳本
腳本介紹:在項目中經常會遇到前台系統頁面卡死、流程周轉時間長、頁面無響應等場景,公司項目開發通常會不假思索的認為數據庫有“死鎖”,然而開發人員往往忽略了數據庫存在事務的概念,如下腳本可以為運維或者開發人員查詢當前數據庫執行的事務信息,方便排查問題。
-
sql_id:sql語句對應唯一id(將sql語句通過MD5以及hash算法計算出來的值)
-
username:sql語句執行的用戶。
-
inst_id:數據庫實例編號,單實例就是1,rac環境會有其他節點
-
sid:sid與serial#組成會話唯一標識。
-
serial#:同上。
-
status
:事務的執行狀態,active代表正在執行,inactive代表空閑狀態。 -
start_time:事務開始的時間。
-
el_second
:事務執行時間,單位是秒。 -
sql_text
:當前事務執行的sql語句文本。 -
prev_sql_text
:當前事務前一條執行的sql文本。
通常公司的OLTP系統中事務執行效率都是非常高的(幾秒內完成),假如發現長事務可能就會出現問題,如下是長事務的幾個例子:
問題樣例(1)
:sql性能較差
,sql_id為4v4mcb0p5mxv3的會話正在執行更新操作,對應事務打開了40秒以上還沒提交,明顯存在性能問題,需要優化SQL。
問題樣例(2)
:事務掛起
,如下2個長事務status為inactive且sql_text為空,事務已經打開了幾百秒還未提交,需要檢查下代碼中是否因為接口問題而沒有正常提交事務。
問題樣例(3)
:BUG引起的長事務
,此案例是通過dblink遠程插入表數據,但是由於Oracle bug,insert的事務被hang住,無法完成插入操作。
Oracle下可以根據腳本查詢結果的sid,serial#,inst_id
來kill會話:
3.2. 行鎖等待腳本
腳本介紹:在很多情況下經常會遇到DML語句被阻塞,導致前台掛起,流程無法流轉下去,以下腳本能夠查詢因為行鎖等待而引起的SQL阻塞。
-
blocking_user:阻塞源頭的數據庫用戶名。
-
blocking_machine:阻塞源頭的服務器機器名。
-
Blocking_SID:阻塞的源頭會話信息。
-
blocking_serialnum:阻塞的源頭會話信息。
-
wait_user:被阻塞的數據庫用戶名。
-
wait_machine:被阻塞的服務器機器名。
-
Wait_SID:被阻塞的會話。
-
wait_serialnum:被阻塞的會話。
-
Wait_Sql
:處於等待狀態,被阻塞的sql語句。 -
object_name
:對象名稱。 -
Kill_Command
:kill阻塞源頭的命令。
以下是更新同一行數據被阻塞的場景,項目中查詢之后可以使用Kill_Command那一列生成的命令,kill會話即可。
四. SQL Server篇
4.1. SQL狀態腳本
腳本介紹:可以看到SQL Server中所有用戶會話的活躍SQL執行情況,包括SQL執行時間,邏輯讀,物理讀,CPU開銷等,也可以看到SQL是否被阻塞。
如果腳本執行報錯:sql_handle 不是可識別的表提示選項。如果它要作為表值函數或 CHANGETABLE 函數的參數,請確保您的數據庫兼容模式設置為 90。則需要修改數據庫-屬性-選項-兼容性級別到SQL Server 2005以上
。
輸出字段解釋:
-
session_id:會話id,可以通過
kill + session_id
結束會話。 -
SQL
:具體的執行SQL,存儲過程等被調用時會直接顯示其定義。 -
db_name:會話所在數據庫名稱。
-
blocking_session_id
:SQL被阻塞的會話,0表示沒有被阻塞,非0表示被阻塞會話ID。 -
duration
:SQL執行時間,單位為毫秒(ms)。 -
client_net_address:客戶端IP。
-
cpu、physicanl_reads、logical_reads、writes
:SQL產生的cpu開銷、物理讀、邏輯讀、寫入。 -
command:命令類型。
-
status
:SQL狀態,running表示正在執行,runnable表示等待cpu調度,suspended
表示等待掛起。 -
wait_type
:等待事件。 -
wait_time
:等待時間。
注意點:sp_trace_getdata表示sqlprofile監控,無需關注。通常我們可以觀察SQL執行時長(duration)來判斷數據庫是否存在性能問題,然后進一步排查SQL阻塞,等待事件找到問題原因。
問題樣例
:服務器CPU較高,腳本排查發現部分SQL邏輯讀、cpu開銷較大,優化SQL后CPU正常。
4.2. 阻塞腳本
腳本介紹:可以看到SQL Server中阻塞的源頭信息,例如發生級聯阻塞,A阻塞B,B阻塞C,C阻塞D,那么通過腳本我們可以看到A阻塞B的信息,即阻塞源頭,只有找到源頭,我們才可以解決阻塞問題。
輸出字段解釋:
-
WaitingSessionID
:被阻塞會話ID。 -
BlockingSessionID
:阻塞會話ID。 -
WaitingSession_TSQL
:被阻塞SQL。 -
BlockingSession_TSQL
:阻塞SQL。 -
WAIT_DURATION_MS
:阻塞時長,單位ms。 -
is_blocked:阻塞會話本身是否被阻塞。
-
STATUS
:阻塞會話狀態,sleeping為處於掛起狀態。 -
wait_type:阻塞會話等待類型。
-
wait_time:阻塞會話等待事件。
注意點:WHERE P.blocked = 0
條件去掉可以看到當前數據庫中的所有阻塞,加上的話只能看到阻塞源頭,下面模擬輸出下:
-
不帶
where P.blocked = 0
條件輸出如下:
-
帶
where P.blocked = 0
條件輸出如下:
-
通過
SQL狀態腳本
輸出如下,與不帶where P.blocked = 0
類似,但是無法看到處於掛起的事務狀態:
4.3. 表統計腳本
腳本介紹:通過腳本,可以看到某個庫下各個表的實際大小,比較實用。大家可以按照實際需求排序輸出,因為數據大小都是自動帶單位的,所以沒法直接排序,這里習慣以data長度降序輸出。
-
rows:表實際行數。
-
reserved:數據庫為該表分配的空間。
-
data:表數據占用的空間。
-
index_size:表索引占用的空間。
-
unused:表上未使用的空間,大致等於reserved - data - index_size的值。