【科普】三大數據庫運維腳本合集(建議收藏)


一. 大綱

在日常的數據庫運維管理中,數據庫腳本是排查問題必不可少的利器,好的運維腳本可以讓我們快速排查出問題所在,並在最短時間內解決問題。

但是在實際項目中,很多項目組人員面對數據庫方面的突發問題,特別是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腳本,排查問題必備。

SELECT
trx_mysql_thread_id as id,concat(t.PROCESSLIST_USER,'@',t.PROCESSLIST_HOST,':',t.PROCESSLIST_DB) As connection,
trx_state,trx_started, concat(timestampdiff(SECOND,trx_started,CURRENT_TIMESTAMP()),'s') AS duration,
trx_query,t.processlist_state as state,sum(h.rows_examined) as trx_rows_examined,trx_rows_locked,trx_rows_modified,trx_isolation_level,
group_concat(h.sql_text order by h.TIMER_START DESC SEPARATOR ';\n') As thd_last_query,t.thread_id
FROM
information_schema.INNODB_TRX b
LEFT JOIN performance_schema.threads t on t.processlist_id = b.trx_mysql_thread_id
LEFT JOIN performance_schema.events_statements_history h USING(thread_id)
where (trx_operation_state != 'sleeping before entering InnoDB' or trx_operation_state is null)
group by thread_id order by trx_started

腳本正常輸出如下,事務都比較短(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解決。

SELECT
concat('kill ',id,';') as kill_sql,concat(user,'@',host,':',db) As connection,command,time,state,info,trx_started
FROM
INFORMATION_SCHEMA.processlist p left join INFORMATION_SCHEMA.INNODB_TRX trx on p.id = trx.trx_mysql_thread_id
WHERE (TO_SECONDS(now())-TO_SECONDS(trx_started) >= (SELECT MAX(Time) FROM INFORMATION_SCHEMA.processlist
WHERE STATE like 'Waiting for%' and command != 'Daemon') or STATE like 'Waiting for%') and command != 'Daemon'
order by trx_started desc,time desc

輸出字段解釋:

  • 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行鎖等待超時問題。

SELECT
r.trx_mysql_thread_id waiting_id,b.trx_mysql_thread_id blocking_id,
concat(timestampdiff(SECOND,r.trx_wait_started,CURRENT_TIMESTAMP()),'s') AS duration,
t.processlist_command state,r.trx_query waiting_query,b.trx_query blocking_current_query,
group_concat(h.sql_text order by h.TIMER_START DESC SEPARATOR ';\n') As thd_last_query
FROM
information_schema.innodb_lock_waits w
JOIN information_schema.innodb_trx b ON b.trx_id = w.blocking_trx_id
JOIN information_schema.innodb_trx r ON r.trx_id = w.requesting_trx_id
LEFT JOIN performance_schema.threads t on t.processlist_id = b.trx_mysql_thread_id
LEFT JOIN performance_schema.events_statements_history h USING(thread_id)
group by thread_id order by r.trx_wait_started

輸出字段解釋:

  • 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. 長事務腳本

腳本介紹:在項目中經常會遇到前台系統頁面卡死、流程周轉時間長、頁面無響應等場景,公司項目開發通常會不假思索的認為數據庫有“死鎖”,然而開發人員往往忽略了數據庫存在事務的概念,如下腳本可以為運維或者開發人員查詢當前數據庫執行的事務信息,方便排查問題。

select
xid,sql_id,username,inst_id, sid,serial#,status,start_time,el_second,sql_text,prev_sql_text
from
( with ltr as (select to_char(sysdate,'YYYYMMDDHH24MISS') TM, s.inst_id, s.username,
s.sid,s.serial#,s.status,s.sql_id,s.sql_child_number,s.prev_sql_id,xid,
t.start_scn,to_char(t.start_date, 'YYYY-MM-DD HH24:MI:SS') start_time,
e.TYPE,e.block,e.ctime/ 3600 runtime_Hour,
decode(e.CTIME, 0, (sysdate - t.start_date) * 3600*24, e.ctime) el_second
from
gv$ transaction t, gv$session s,gv$transaction_enqueue e
where t.start_date <= sysdate - interval '1' second and t.addr = s.taddr and t.addr = e.addr(+) )
select ltr.* ,(select q1.sql_text from gv$sql q1 where ltr.prev_sql_id = q1.sql_id(+) and rownum = 1) prev_sql_text ,
( select q1.sql_text from gv$sql q1 where ltr.sql_id = q1.sql_id(+)
and ltr.sql_child_number = q1.CHILD_NUMBER(+) and rownum=1) sql_text from ltr ltr)
  • 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會話:

--kill語法
alter system kill session 'sid,serial#,@inst_id' immediate;
 
--單實例下kill會話,可以隱藏實例號
alter system kill session '206,3237' immediate;
 
--rac環境下kill會話,需要標注實例號
alter system kill session '206,3237,@1' immediate;
alter system kill session '206,3237,@2' immediate;
3.2. 行鎖等待腳本

腳本介紹:在很多情況下經常會遇到DML語句被阻塞,導致前台掛起,流程無法流轉下去,以下腳本能夠查詢因為行鎖等待而引起的SQL阻塞。

select s1.username blocking_user,s1.machine blocking_machine,
s1.sid blocking_sid,s1.serial# blocking_serialnum,
chr(10),
s2.username wait_user,s2.machine wait_machine,
s2.sid wait_sid,s2.serial# wait_serialnum,
substr(w1.sql_text,1,50) wait_sql,
do.object_name obj_name,
'ALTER SYSTEM KILL SESSION ''' || s1.sid || ',' || s1.serial# ||
''' IMMEDIATE;' Kill_Command
from v$lock l1,
v$ session s1,
v$ lock l2,
v$ session s2,
v$locked_object lo,
v$sqlarea b1,
v$sqlarea w1,
dba_objects do
where s1.sid=l1.sid
and s2.sid=l2.sid
and l1.id1=l2.id1
and s1.sid=lo.session_id
and lo.object_id=do.object_id
and l1.block =1
and s1.prev_sql_addr=b1.address
and s2.sql_address=w1.address
and l2.request >0;
  • 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以上

select session_id,(select text from sys.dm_exec_sql_text(sql_handle)) as SQL,
( select DB_NAME(database_id)) as db_name,blocking_session_id,
( select top 1 client_net_address from sys.dm_exec_connections where session_id = sys.dm_exec_requests.session_id) as client_net_address,
total_elapsed_time as duration,cpu_time as cpu,reads as physical_reads ,logical_reads as logical_reads,writes,command,
status,wait_type,wait_time,last_wait_type,open_transaction_count,transaction_isolation_level,
percent_complete,estimated_completion_time from sys.dm_exec_requests
where session_id > 50 and status != 'background' order by duration desc,cpu desc,reads desc

輸出字段解釋:

  • 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的信息,即阻塞源頭,只有找到源頭,我們才可以解決阻塞問題。

SELECT R1.session_id AS WaitingSessionID
,S.session_id AS BlockingSessionID
,Q1.TEXT AS WaitingSession_TSQL
,Q2.TEXT AS BlockingSession_TSQL
,R1.wait_time AS WAIT_DURATION_MS
,P.blocked AS is_blocked
,P.STATUS
,R2.wait_time
,R2.wait_type
,R2.total_elapsed_time
,P.open_tran
,P.cmd
,( SELECT db_name(R1.database_id)) AS DBName
,S.original_login_name AS BlockingSession_LoginName
,S.program_name AS BlockingSession_ApplicationName
,S.host_name AS BlockingSession_HostName
FROM sys.dm_exec_requests AS R1
INNER JOIN sys.dm_exec_sessions AS S ON R1.blocking_session_id = S.session_id
INNER JOIN sys.dm_exec_connections AS C1 ON R1.session_id = C1.most_recent_session_id
INNER JOIN sys.dm_exec_connections AS C2 ON S.session_id = C2.most_recent_session_id
INNER JOIN sys.sysprocesses AS P ON S.session_id = P.spid
LEFT JOIN sys.dm_exec_requests AS R2 ON R2.session_id = R1.blocking_session_id
CROSS APPLY sys.dm_exec_sql_text(C1.most_recent_sql_handle) AS Q1
CROSS APPLY sys.dm_exec_sql_text(C2.most_recent_sql_handle) AS Q2
WHERE P.blocked = 0

輸出字段解釋:

  • 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條件去掉可以看到當前數據庫中的所有阻塞,加上的話只能看到阻塞源頭,下面模擬輸出下:

--A會話處於事務掛起狀態
1) begin transaction;
2) update a set id = 100 where id = 1;
3) Sleep;
 
--B會話被A會話阻塞
update a set id = 200 where id = 1;
 
--C會話被B會話阻塞
delete a where id = 1;
  1. 不帶where P.blocked = 0條件輸出如下:

  1. where P.blocked = 0條件輸出如下:

  1. 通過SQL狀態腳本輸出如下,與不帶where P.blocked = 0類似,但是無法看到處於掛起的事務狀態:

4.3. 表統計腳本

腳本介紹:通過腳本,可以看到某個庫下各個表的實際大小,比較實用。大家可以按照實際需求排序輸出,因為數據大小都是自動帶單位的,所以沒法直接排序,這里習慣以data長度降序輸出。

create table #tmp(
name varchar(50),
rows int,
reserved varchar(50),
data varchar(50),
index_size varchar(50),
unused varchar(50)
);
 
insert into #tmp (
name, rows, reserved, data, index_size, unused
) exec sp_MSforeachtable @command1= "sp_spaceused '?'";
 
select * from #tmp where name <> 'tmp' order by len(data) desc ;
 
drop table #tmp ;
  • rows:表實際行數。

  • reserved:數據庫為該表分配的空間。

  • data:表數據占用的空間。

  • index_size:表索引占用的空間。

  • unused:表上未使用的空間,大致等於reserved - data - index_size的值。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM