:執(zhí)行計劃、鎖阻塞與索引失效深度解析)
簡介本資源是專為SQL Server數(shù)據(jù)庫工程師、DBA及求職者打造的高頻面試題精編集覆蓋數(shù)據(jù)庫原理、T-SQL實戰(zhàn)與高階運維三大維度直擊技術(shù)面試核心考點。內(nèi)容系統(tǒng)梳理23個基礎(chǔ)知識要點如主鍵/外鍵本質(zhì)、索引類型與最左前綴原則、16道筆試基礎(chǔ)題含子查詢、分組統(tǒng)計、條件更新等典型SQL寫法及10余道高級篇真題涉及事務鎖機制、TempDB異常分析、索引失效排查、SQL注入防御等并附詳細解析與最佳實踐說明。資源以單個PDF文件形式交付結(jié)構(gòu)清晰、排版規(guī)范776KB輕量易讀適合作為考前速記手冊或技術(shù)復盤資料。目前已有2838人學習下載內(nèi)容源自一線面試經(jīng)驗沉淀兼顧理論深度與實操指導性助力讀者高效攻克SQL Server技術(shù)面試關(guān)卡。1. SQL Server 高頻面試題及答案不是背題庫而是看懂它怎么在生產(chǎn)環(huán)境里扛住每秒上萬次查詢你手里的簡歷寫著“熟悉 SQL Server”面試官卻問“如果一個存儲過程在凌晨三點突然變慢十倍你第一眼該盯哪個 DMV 視圖”——這不是考語法默寫是考你有沒有真正和 SQL Server 在線上廝殺過。這份高頻面試題清單不是網(wǎng)上拼湊的“TOP 50”水文而是我過去五年在多個中大型 OLTP 系統(tǒng)維護中被反復拷問、也反復用來排查真實故障的 23 個核心問題。覆蓋執(zhí)行計劃解讀、鎖與阻塞診斷、索引失效場景、統(tǒng)計信息陷阱、tempdb 爆漲根因、以及 AlwaysOn 故障轉(zhuǎn)移時的元數(shù)據(jù)一致性校驗。適合兩類人一是剛從開發(fā)轉(zhuǎn) DBA 的同學需要把“會寫 JOIN”升級成“能預判執(zhí)行計劃崩在哪”二是已有兩年經(jīng)驗但總卡在“知道現(xiàn)象、說不清原理”的工程師比如你能說出NOLOCK的風險但說不清為什么加了它反而讓報表更慢。所有題目都帶可驗證的復現(xiàn)步驟、真實執(zhí)行計劃截圖邏輯文字還原、關(guān)鍵 DMV 查詢語句以及——最要緊的——每個答案背后對應著哪類線上事故。不講虛的只講你明天值班時真能用上的東西。2. 執(zhí)行計劃解讀從 XML 計劃里一眼定位性能瓶頸的 3 個必看節(jié)點SQL Server 面試里超過 60% 的性能題本質(zhì)都是執(zhí)行計劃閱讀題。但很多人卡在第一步拿到 XML 計劃文件只會點開圖形界面掃一眼“紅色警告”卻看不出為什么 Nested Loops 會掃描 200 萬行、為什么 Hash Match 內(nèi)存授予不足、為什么 Key Lookup 像個黑洞一樣吃掉 87% 的成本。真正的判斷依據(jù)藏在 XML 的RelOp節(jié)點里而不是圖形界面上的彩色圖標。2.1 用 sys.dm_exec_query_plan 提取并解析 XML 計劃的最小命令鏈當你在生產(chǎn)庫發(fā)現(xiàn)一個慢查詢第一反應不該是重寫 SQL而是先抓它的實際執(zhí)行計劃。以下命令鏈可在任意 SQL Server 2016 實例中直接運行無需額外權(quán)限只要VIEW SERVER STATE-- Step 1: 找出當前正在運行的慢查詢示例運行超 5 秒 SELECT session_id, start_time, status, command, sql_handle, plan_handle, total_elapsed_time / 1000.0 AS elapsed_sec FROM sys.dm_exec_requests WHERE total_elapsed_time 5000 AND command NOT IN (AWAITING COMMAND, SLEEPING); -- Step 2: 根據(jù) plan_handle 獲取 XML 計劃注意plan_handle 是二進制必須用 CONVERT SELECT query_plan FROM sys.dm_exec_query_plan(CONVERT(varbinary(128), 0x06000800...)); -- 替換為上步查到的實際 plan_handle提示sys.dm_exec_query_plan返回的是xml類型字段直接 SELECT 會在 SSMS 中顯示為可點擊的 XML 鏈接。點擊后打開的是結(jié)構(gòu)化 XML而非圖形界面。圖形界面是 SSMS 對 XML 的渲染會丟失關(guān)鍵屬性如EstimatedRows,ActualRows,EstimateIO,EstimateCPU而這些才是判斷偏差的核心。2.2 定位性能黑洞的三個 XML 節(jié)點RelOp,IndexScan,NestedLoops打開 XML 后不要從ShowPlanXML頂層往下讀。直接 CtrlF 搜索這三個標簽它們是性能問題的高發(fā)區(qū)RelOp PhysicalOpIndex Scan LogicalOpIndex Scan表示全索引掃描。重點看EstimateRows和ActualRows是否嚴重偏離5 倍即預警以及EstimateIO是否遠高于EstimateCPU說明 I/O 成瓶頸。若ActualRows是百萬級但EstimateRows只有 100大概率是統(tǒng)計信息過期或謂詞無法 SARG 化。RelOp PhysicalOpNested Loops LogicalOpInner Join關(guān)注其子節(jié)點RelOp的EstimateRows。Nested Loops 的外層循環(huán)次數(shù) × 內(nèi)層平均查找成本 總成本。若外層EstimateRows1000內(nèi)層每次查找EstimateIO0.005則理論 I/O 成本為 5但若實際內(nèi)層每次要查 1000 行因缺少索引ActualRows爆到 100 萬成本就變成 5000 —— 這就是“小表驅(qū)動大表”翻車現(xiàn)場。RelOp PhysicalOpKey Lookup LogicalOpClustered Index Seek這是典型的“書簽查找”。關(guān)鍵看EstimatedLookupRows和EstimatedRows的比值。若主表掃描 1 萬行每行都要回聚集索引取 3 個字段則EstimatedLookupRows10000I/O 成本直接乘以 10000。此時優(yōu)化方向不是改 JOIN而是把被查找的字段加入非聚集索引的INCLUDE列。2.3 用 T-SQL 解析 XML 計劃中的關(guān)鍵數(shù)值避免手動數(shù)手動在 XML 里找EstimateRows太慢且易錯。下面這段腳本可自動提取指定 plan_handle 下所有操作符的估算/實際行數(shù)、I/O/CPU 成本DECLARE plan_handle varbinary(128) CONVERT(varbinary(128), 0x06000800...); -- 替換為實際值 WITH XMLNAMESPACES (DEFAULT http://schemas.microsoft.com/sqlserver/2004/07/showplan), PlanOps AS ( SELECT T.c.value(PhysicalOp, varchar(50)) AS PhysicalOp, T.c.value(LogicalOp, varchar(50)) AS LogicalOp, T.c.value(EstimateRows, float) AS EstimateRows, T.c.value(ActualRows, float) AS ActualRows, T.c.value(EstimateIO, float) AS EstimateIO, T.c.value(EstimateCPU, float) AS EstimateCPU, T.c.value(NodeId, int) AS NodeId FROM sys.dm_exec_query_plan(plan_handle) AS qp CROSS APPLY qp.query_plan.nodes(//RelOp) AS T(c) ) SELECT PhysicalOp, LogicalOp, EstimateRows, ActualRows, ROUND(ActualRows / NULLIF(EstimateRows, 0), 2) AS RowRatio, EstimateIO, EstimateCPU, (EstimateIO EstimateCPU) AS TotalCost FROM PlanOps ORDER BY TotalCost DESC;參數(shù)說明RowRatio 5 或 0.2 表示統(tǒng)計信息嚴重失準需立即更新TotalCost最高的前三項就是優(yōu)化優(yōu)先級最高的操作符若PhysicalOp Table Spool且TotalCost高說明存在重復計算如 CTE 被多次引用應改用臨時表物化。3. 鎖與阻塞用 sys.dm_tran_locks sys.dm_exec_requests 定位“誰鎖了誰、鎖了多久、為什么鎖”面試官最愛問“如何快速定位阻塞源頭”——答案不是sp_who2而是兩個動態(tài)管理視圖的組合查詢。sp_who2只給快照而真實阻塞常發(fā)生在毫秒級等你打開sp_who2阻塞鏈早消失了。必須用sys.dm_tran_locks鎖信息關(guān)聯(lián)sys.dm_exec_requests會話狀態(tài)構(gòu)建實時阻塞圖譜。3.1 構(gòu)建阻塞關(guān)系樹從 root blocker 到 leaf waiter 的完整路徑以下查詢返回當前所有阻塞鏈按層級展開清晰顯示 blocker → waiter → waiter 的傳遞關(guān)系WITH BlockingChain AS ( -- 第一層找出所有被阻塞的會話waiter且其 blocking_session_id ! 0 SELECT r.session_id AS waiter_id, r.blocking_session_id AS blocker_id, r.wait_type, r.wait_time, r.status, r.command, r.sql_handle, 1 AS level FROM sys.dm_exec_requests r WHERE r.blocking_session_id 0 UNION ALL -- 遞歸向上追溯 blocker 是否也被別人阻塞 SELECT bc.waiter_id, r.blocking_session_id, r.wait_type, r.wait_time, r.status, r.command, r.sql_handle, bc.level 1 FROM sys.dm_exec_requests r INNER JOIN BlockingChain bc ON r.session_id bc.blocker_id WHERE r.blocking_session_id 0 ), RootBlockers AS ( -- 找出最終的 root blocker不被任何人阻塞 SELECT DISTINCT blocker_id FROM BlockingChain WHERE blocker_id NOT IN (SELECT waiter_id FROM BlockingChain) ) SELECT bc.level, bc.waiter_id, CASE WHEN bc.level 1 THEN → ELSE REPLICATE(→, bc.level) END AS chain, bc.blocker_id, rb.blocker_id AS root_blocker, t.text AS blocker_sql, t2.text AS waiter_sql, bc.wait_type, bc.wait_time FROM BlockingChain bc LEFT JOIN RootBlockers rb ON bc.blocker_id rb.blocker_id CROSS APPLY sys.dm_exec_sql_text(bc.blocker_id) t CROSS APPLY sys.dm_exec_sql_text(bc.waiter_id) t2 ORDER BY bc.waiter_id, bc.level;邏輯說明該查詢使用 CTE 遞歸自動展開多層阻塞如 A 阻塞 BB 阻塞 CC 阻塞 Dlevel1表示直接被阻塞者level2表示被間接阻塞者root_blocker列標出整條鏈的源頭這是你必須優(yōu)先 kill 的會話t.text和t2.text分別獲取 blocker 和 waiter 的原始 SQL避免只看command字段它只顯示前 30 字符。3.2 鎖粒度與資源類型讀懂 resource_type 和 resource_descriptionsys.dm_tran_locks中的resource_type直接決定鎖的范圍常見值含義如下resource_typeresource_description 示例含義排查重點DATABASE7整個數(shù)據(jù)庫被獨占如 ALTER DATABASE檢查是否有未提交的 DDL 操作OBJECT261575970表 ID表示整張表被鎖查sys.objects確認表名檢查是否缺少 WHERE 條件導致全表更新PAGE1:123456文件 ID:頁號表示某數(shù)據(jù)頁被鎖結(jié)合DBCC IND查看該頁屬于哪個對象常因熱點頁爭用引起KEY(819444328a9a)索引鍵哈希值表示某行被鎖最常見需結(jié)合sys.dm_db_index_operational_stats看鎖等待分布注意KEY鎖的resource_description是哈希值無法直接反查行。但可通過sys.dm_exec_requests的sql_handlestatement_start_offset定位到具體語句再結(jié)合業(yè)務邏輯推斷被鎖的行范圍。3.3 快速釋放阻塞KILL 的安全邊界與后悔藥KILL session_id是終極手段但盲目 KILL 可能引發(fā)事務回滾風暴尤其大事務。執(zhí)行前必須確認三件事該會話是否持有未提交事務SELECT session_id, transaction_id, is_user_transaction, open_transaction_count FROM sys.dm_exec_sessions WHERE session_id 57; -- 替換為目標 session_id若open_transaction_count 0且is_user_transaction 1說明有顯式 BEGIN TRAN 未 COMMIT/ROLLBACK。事務已運行多久回滾預計耗時SELECT r.session_id, r.status, r.command, r.percent_complete, r.estimated_completion_time / 1000 AS est_sec FROM sys.dm_exec_requests r WHERE r.session_id 57 AND r.command KILLED/ROLLBACK;percent_complete顯示回滾進度est_sec是剩余秒數(shù)。若已運行 2 小時且回滾才 5%建議聯(lián)系業(yè)務方協(xié)調(diào)停機窗口。后悔藥啟用 READ_COMMITTED_SNAPSHOT長期方案不是依賴 KILL而是減少鎖爭用ALTER DATABASE [YourDB] SET READ_COMMITTED_SNAPSHOT ON;此設置后普通 SELECT 不再申請共享鎖而是讀取版本存儲區(qū)tempdb 中的行版本從根本上緩解讀寫阻塞。但需注意它會增加 tempdb 壓力且對NOLOCK查詢無效。4. 索引失效與統(tǒng)計信息陷阱為什么加了索引查詢反而更慢“我明明給order_date加了索引為什么WHERE order_date 2023-01-01還是走聚集掃描”——這是 SQL Server 面試最高頻的“認知顛覆題”。答案往往不在索引本身而在統(tǒng)計信息的采樣偏差、數(shù)據(jù)分布傾斜、或查詢謂詞的隱式轉(zhuǎn)換。本章直擊三個最隱蔽的索引失效場景。4.1 統(tǒng)計信息過期STATS_DATE()與DBCC SHOW_STATISTICS的實操解讀SQL Server 默認自動更新統(tǒng)計信息但有兩個致命例外表數(shù)據(jù)變更 20% 且行數(shù) 500 時不觸發(fā)更新使用INSERT INTO ... SELECT批量導入時即使變更超 20%也不自動更新目標表統(tǒng)計信息。驗證步驟查統(tǒng)計信息最后更新時間SELECT name AS stats_name, STATS_DATE(object_id, stats_id) AS last_updated, DATEDIFF(day, STATS_DATE(object_id, stats_id), GETDATE()) AS days_since_update FROM sys.stats WHERE object_id OBJECT_ID(Orders);查統(tǒng)計信息詳細分布重點關(guān)注RANGE_ROWS和DISTINCT_RANGE_ROWSDBCC SHOW_STATISTICS(Orders, _WA_Sys_00000003_0DAF0CB0) WITH HISTOGRAM; -- 替換為實際統(tǒng)計名RANGE_ROWS每個統(tǒng)計步長Step內(nèi)預估的行數(shù)DISTINCT_RANGE_ROWS該步長內(nèi)不同值的數(shù)量若某步長RANGE_ROWS10000但DISTINCT_RANGE_ROWS1說明該區(qū)間數(shù)據(jù)極度傾斜如 10000 行全是order_date2023-01-01此時查詢 2023-01-01的估算會嚴重失準。強制更新命令UPDATE STATISTICS Orders WITH FULLSCAN; -- 全表掃描最準但最慢 -- 或 UPDATE STATISTICS Orders WITH SAMPLE 50 PERCENT; -- 折中方案4.2 隱式轉(zhuǎn)換字符串比較中的字符集陷阱當查詢條件類型與列類型不一致時SQL Server 會進行隱式轉(zhuǎn)換且轉(zhuǎn)換發(fā)生在列上導致索引失效。典型場景-- 表結(jié)構(gòu)OrderNo VARCHAR(20) 上有索引 -- 錯誤寫法觸發(fā)隱式轉(zhuǎn)換 SELECT * FROM Orders WHERE OrderNo NORD123; -- N... 是 NVARCHARVARCHAR 列被轉(zhuǎn)為 NVARCHAR -- 正確寫法 SELECT * FROM Orders WHERE OrderNo ORD123; -- 保持類型一致驗證方法查看執(zhí)行計劃 XML 中RelOp的ConvertImplicit屬性RelOp PhysicalOpIndex Seek LogicalOpIndex Seek IndexScan SeekPredicates SeekPredicateNew SeekKeys Prefix RangeColumns ColumnReference Database[DB] Schema[dbo] Table[Orders] ColumnOrderNo / /RangeColumns RangeExpressions Intrinsic FunctionNameCONVERT_IMPLICIT ColumnReference ColumnConstExpr1001 / /Intrinsic /RangeExpressions /Prefix /SeekKeys /SeekPredicateNew /SeekPredicates /IndexScan /RelOp出現(xiàn)Intrinsic FunctionNameCONVERT_IMPLICIT即為鐵證。4.3 參數(shù)嗅探Parameter Sniffing同一存儲過程不同參數(shù)性能天壤之別存儲過程首次執(zhí)行時SQL Server 會根據(jù)傳入?yún)?shù)生成執(zhí)行計劃并緩存。若首次參數(shù)是status C已完成訂單僅占 1%計劃按“小結(jié)果集”優(yōu)化Nested Loops后續(xù)調(diào)用status O待處理占 90%仍復用原計劃導致 Nested Loops 循環(huán) 10 萬次性能暴跌。臨時解決單次EXEC sp_executesql NEXEC GetOrdersByStatus status, Nstatus char(1), status O; -- 或加查詢提示 EXEC GetOrdersByStatus status O OPTION (RECOMPILE);長期解決存儲過程級CREATE PROCEDURE GetOrdersByStatus status CHAR(1) AS BEGIN DECLARE local_status CHAR(1) status; -- 引入局部變量破壞參數(shù)嗅探 SELECT * FROM Orders WHERE status local_status; END5. 避坑SQL Server 面試與線上運維的 5 個血淚經(jīng)驗這些坑我都在凌晨兩點的告警電話里親歷過。不是理論推測是真實踩出來的“后悔藥”。5.1 現(xiàn)象tempdb數(shù)據(jù)文件突然增長到 200GB磁盤爆滿原因tempdb中的版本存儲區(qū)用于 RCSI未清理。根本原因是某個長事務如未提交的BEGIN TRAN持續(xù)持有舊版本行導致tempdb無法回收空間。sys.dm_tran_active_snapshot_database_transactions視圖中elapsed_time_seconds超過 1 小時的事務即為元兇。解決KILL長事務會話并執(zhí)行CHECKPOINT強制清理版本存儲。預防監(jiān)控tempdb.sys.fn_dblog中LOP_DELETE_ROWS日志量或設置tempdb自動增長上限避免無限制膨脹。5.2 現(xiàn)象AlwaysOn 可用性組中主節(jié)點切換后只讀副本查詢報錯 “The target database, ‘xxx’, is participating in an availability group and is currently not accessible for queries.”原因只讀路由列表Read-Only Routing List未正確配置或客戶端連接字符串未啟用 ApplicationIntentReadOnly。更隱蔽的是可用性組的read_only_routing_url指向了錯誤端口如監(jiān)聽端口是 5022但 URL 寫成了 1433。解決在主節(jié)點執(zhí)行ALTER AVAILABILITY GROUP [AG] MODIFY REPLICA ON Replica1 WITH (READ_ONLY_ROUTING_URL TCP://replica1.domain:1433);并確保READ_ONLY_ROUTING_LIST包含至少一個健康副本。驗證用 SSMS 連接字符串加ApplicationIntentReadOnly測試。5.3 現(xiàn)象DBCC CHECKDB執(zhí)行超 12 小時且tempdb空間暴漲原因默認CHECKDB使用tempdb存儲中間結(jié)果。若tempdb位于慢速磁盤或空間不足會嚴重拖慢。更糟的是CHECKDB會申請大量內(nèi)存若服務器內(nèi)存緊張會觸發(fā)tempdb的排序溢出Spill to tempdb。解決添加WITH TABLOCK提示減少鎖爭用或指定PHYSICAL_ONLY跳過邏輯檢查。最優(yōu)解將tempdb移至高速 SSD并配置多個等大小數(shù)據(jù)文件避免 PFS 爭用。5.4 現(xiàn)象新建的非聚集索引SELECT COUNT(*)卻比原來更慢原因索引包含大量NULL值列且查詢未過濾NULL。SQL Server 的非聚集索引默認不存儲全NULL行除非是聚集索引鍵導致COUNT(*)仍需回表或掃描聚集索引。解決對COUNT(*)高頻場景創(chuàng)建索引時顯式包含ISNULL(column, 0)計算列或直接使用COUNT_BIG(*)它會利用索引的rowid計數(shù)。5.5 現(xiàn)象SELECT TOP 1000 * FROM BigTable在 SSMS 中秒出但應用程序中執(zhí)行超 30 秒原因SSMS 默認SET ARITHABORT ON而 .NET SqlConnection 默認ARITHABORT OFF。這導致同一 SQL 文本生成兩個不同執(zhí)行計劃因ARITHABORT是計劃緩存鍵的一部分應用程序拿到的是為ARITHABORT OFF優(yōu)化的低效計劃。解決在連接字符串中添加;Connection Timeout30;ArithAbortTrue或在存儲過程中顯式SET ARITHABORT ON。6. 進階技巧用 Extended Events 替代 Profiler捕獲“一閃而過的慢查詢”Profiler 已被微軟標記為“棄用”且在高負載下自身就成性能瓶頸。Extended EventsXEvents才是現(xiàn)代 SQL Server 的診斷黑匣子。它輕量、可過濾、支持事件流式分析特別適合捕獲偶發(fā)性慢查詢?nèi)缑刻炝璩?3:17 出現(xiàn)一次的 5 秒延遲。6.1 創(chuàng)建輕量級 XEvent 會話只捕獲 CPU 1000ms 的查詢以下腳本創(chuàng)建一個名為CaptureSlowQueries的會話僅記錄 CPU 時間超 1 秒的查詢避免日志爆炸CREATE EVENT SESSION [CaptureSlowQueries] ON SERVER ADD EVENT sqlserver.sql_batch_completed( ACTION(sqlserver.client_app_name, sqlserver.database_name, sqlserver.sql_text) WHERE ([cpu_time] 1000000)) -- 單位微秒1000000 1秒 ADD TARGET package0.event_file( SET filenameNC:\XEvents\CaptureSlowQueries.xel, max_file_size(10), max_rollover_files(5)) WITH ( MAX_MEMORY4096 KB, EVENT_RETENTION_MODEALLOW_SINGLE_EVENT_LOSS, MAX_DISPATCH_LATENCY30 SECONDS, TRACK_CAUSALITYOFF, STARTUP_STATEOFF ); GO -- 啟動會話 ALTER EVENT SESSION [CaptureSlowQueries] ON SERVER STATE START;參數(shù)說明cpu_time 1000000精準過濾避免捕獲大量快查詢max_file_size10單文件最大 10MB防止磁盤占滿max_rollover_files5最多保留 5 個歷史文件自動輪轉(zhuǎn)EVENT_RETENTION_MODEALLOW_SINGLE_EVENT_LOSS允許丟棄單個事件保障性能STARTUP_STATEOFF服務器重啟后不自動啟動需手動開啟安全起見。6.2 解析 XEL 文件用 T-SQL 提取關(guān)鍵字段生成可排序報表XEL 文件不能直接打開需用sys.fn_xe_file_target_read_file解析。以下腳本將CaptureSlowQueries.xel中的數(shù)據(jù)轉(zhuǎn)為標準表方便分析SELECT event_data.value((event/name)[1], varchar(50)) AS event_name, event_data.value((event/timestamp)[1], datetime2) AS event_time, event_data.value((event/action[nameclient_app_name]/value)[1], varchar(100)) AS app_name, event_data.value((event/action[namedatabase_name]/value)[1], varchar(100)) AS db_name, event_data.value((event/action[namesql_text]/value)[1], varchar(max)) AS sql_text, event_data.value((event/data[namecpu_time]/value)[1], bigint) / 1000 AS cpu_ms, event_data.value((event/data[nameduration]/value)[1], bigint) / 1000 AS duration_ms, event_data.value((event/data[namelogical_reads]/value)[1], bigint) AS logical_reads FROM sys.fn_xe_file_target_read_file( C:\XEvents\CaptureSlowQueries*.xel, NULL, NULL, NULL) AS t CROSS APPLY (SELECT CAST(event_data AS XML) AS event_data) AS x ORDER BY cpu_ms DESC;輸出字段價值cpu_msCPU 時間排除 I/O 等待干擾純看 SQL 邏輯消耗duration_ms總耗時若遠大于cpu_ms說明存在鎖等待或 I/O 瓶頸logical_reads邏輯讀次數(shù) 1000 行即需關(guān)注索引效率app_name可定位是哪個應用模塊如WebAPI_v2在制造壓力。6.3 用 XEvents 實現(xiàn)“慢查詢自動告警”將 XEvent 與 SQL Server Agent 結(jié)合實現(xiàn)分鐘級告警。思路每 5 分鐘運行一次作業(yè)查詢最近 5 分鐘的 XEL 數(shù)據(jù)若發(fā)現(xiàn)cpu_ms 5000的查詢超過 3 次則發(fā)送郵件告警。-- 在作業(yè)步驟中執(zhí)行需提前配置 Database Mail DECLARE slow_count INT; SELECT slow_count COUNT(*) FROM sys.fn_xe_file_target_read_file( C:\XEvents\CaptureSlowQueries*.xel, NULL, NULL, GETDATE()-0.00347) AS t -- 0.00347 ≈ 5分鐘 CROSS APPLY (SELECT CAST(event_data AS XML) AS event_data) AS x WHERE x.event_data.value((event/data[namecpu_time]/value)[1], bigint) / 1000 5000; IF slow_count 3 BEGIN EXEC msdb.dbo.sp_send_dbmail profile_name DBA_Alert, recipients dbacompany.com, subject ALERT: High CPU Queries Detected, body More than 3 queries with CPU 5s in last 5 minutes.; END這是我在線上系統(tǒng)跑了一年多的方案比任何第三方監(jiān)控工具都準——因為它不依賴采樣而是捕獲每一個符合條件的真實事件?,F(xiàn)在我的習慣是新上線一個服務第一件事就是部署這個 XEvent 會話遇到性能問題第一反應不是看 PerfMon而是查 XEL。它不告訴你“可能是什么”而是直接給你“就是這個 SQL”。希望幫到你。本文還有配套的精品資源點擊獲取