SQLServer效能優化之---資料庫級日記監控
上節回顧:https://www.cnblogs.com/dotnetcrazy/p/11029323.html
4.6.6.SQLServer監控
指令碼示意:https://github.com/lotapp/BaseCode/tree/master/database/SQL/SQLServer
PS:這些指令碼都是我以前用SQLServer手寫的,參考即可(現在用MySQL,下次也整理一下)
之前寫SQLServer監控系列文章因為換環境斷篇了,只是簡單演示了下基礎功能,現在準備寫MySQL
監控相關內容了,於是補了下:
SQLServer效能優化之---資料庫級日記監控:https://www.cnblogs.com/dunitian/p/6022967.html
在說監控前你可以先看下資料庫發郵件:https://www.cnblogs.com/dunitian/p/6022826.html
應用:一般就是設定個定時任務,把耗時SQL資訊或者錯誤資訊通過郵件的方式及時預警
好處就太多了,eg:客戶出錯如果是資料庫層面,那瞬間就可以場景重放(PS:等客戶找會降低業績)
以往都是程式的try
+catch
來捕獲錯誤,但資料庫定時任務之類的出錯程式是捕獲不到的,所以就需要資料庫層面的監控了
PS:開發的時候通過
SQLServer Profiler
來監控
先說說本質吧:SQLServer2012的XEVENT機制已經完善,eg:常用的擴充套件事件error_reported
PS:擴充套件事件效能較高,而且比較輕量級
PS:SQLServer的監控大體思路三步走:發郵件
,事件監控
,定時執行
4.6.6.1 傳送郵件
這個之前講過,這邊就再說下SQL的方式:
1.配置發件人郵箱
這個配置一次即可,以後使用就可以直接通過配置名發郵件:
--開啟發郵件功能 exec sp_configure 'show advanced options',1 reconfigure with override go exec sp_configure 'database mail xps',1 reconfigure with override go --建立郵件帳戶資訊 exec msdb.dbo.sysmail_add_account_sp @account_name ='dunitian', -- 郵件帳戶名稱 @email_address ='[email protected]', -- 發件人郵件地址 @display_name ='SQLServer2014_192.168.36.250', -- 發件人姓名 @MAILSERVER_NAME = 'smtp.163.com', -- 郵件伺服器地址 @PORT =25, -- 郵件伺服器埠 @USERNAME = '[email protected]', -- 使用者名稱 @PASSWORD = '郵件密碼或授權碼' -- 密碼(授權碼) GO --資料庫配置檔案 exec msdb.dbo.sysmail_add_profile_sp @profile_name = 'SQLServer_DotNetCrazy', -- 配置名稱 @description = '資料庫郵件配置檔案' -- 配置描述 go --使用者和郵件配置檔案相關聯 exec msdb.dbo.sysmail_add_profileaccount_sp @profile_name = 'SQLServer_DotNetCrazy', -- 配置名稱 @account_name = 'dunitian', -- 郵件帳戶名稱 @sequence_number = 1 -- account 在 profile 中順序(預設是1) go
2.發生預警郵箱
同樣我只演示SQL的方式,圖形化的方式可以看我以前寫的文章:
-- 發郵件測試
exec msdb.dbo.sp_send_dbmail
@profile_name = 'SQLServer_DotNetCrazy', --配置名稱
@recipients = '[email protected]', --收件郵箱
@body_format = 'HTML', --內容格式
@subject = '文章標題', --文章標題
@body = '郵件內容<br/><h2>This is Test</h2>...' --郵件內容
效果:
3.郵件查詢相關
主要用途其實就是出錯排查:
-- 查詢相關
select * from msdb.dbo.sysmail_allitems --檢視所有郵件訊息
select * from msdb.dbo.sysmail_mailitems --檢視郵件訊息(更多列)
select * from msdb.dbo.sysmail_sentitems --檢視已傳送的訊息
select * from msdb.dbo.sysmail_faileditems --失敗狀態的訊息
select * from msdb.dbo.sysmail_unsentitems --看未傳送的訊息
select * from msdb.dbo.sysmail_event_log --檢視記錄日記
4.6.6.2.監控實現
會了郵件的傳送,那下面就是監控了
1.圖形化演示
不推薦使用圖形化的方式,但可以來理解擴充套件事件的監控
1.新建一個會話嚮導(熟悉後可以直接新建會話)
2.設定需要捕獲的擴充套件事件
3.這邊捕獲的全域性欄位和左邊SQL是一樣的(截圖全太麻煩了,所以偷個懶,後面會說怎麼生成左邊的核心SQL)
4.自己根據伺服器效能設定一個合理的值(IO、記憶體、CPU)
5.生成核心SQL(我們圖形化的目的就是生成核心SQL,後面可以根據這個SQL自己擴充套件)
6.核心程式碼如下
7.啟動會話後一個簡單的擴充套件事件監控就有了
8.SQLServer提供了檢視方式
9.日誌可以自己查下xxx\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\Log
2.SQL的方式
上面只是過家家,主要目的就是讓大家知道核心SQL是怎麼來的,憑什麼這麼寫
下面就來個制定化監控:
先截圖演示下各個核心點,然後貼一個我封裝的儲存過程附件
1.擴充套件事件相關的核心程式碼
2.記憶體中資料儲存到臨時表
3.臨時表中的資料儲存到自己建立的表中
我拋一個課後小問給大家:為什麼先儲存在臨時表中?(提示:效率)
4.傳送監控提醒的郵件
5.看看資料庫層面多了什麼:
6.來個測試
7.效果(可以自己美化)
SQL附錄
-- 切換到需要監控的資料庫
USE [dotnetcrazy]
GO
--收集伺服器上邏輯錯誤的資訊
SET QUOTED_IDENTIFIER ON
SET ANSI_NULLS ON
GO
-- 自定義的錯誤資訊表
IF OBJECT_ID('log_error_message') IS NULL
BEGIN
CREATE TABLE [dbo].[log_error_message]
(
[login_message_id] [uniqueidentifier] NULL CONSTRAINT [DF__PerfLogic__Login__7ACA4E21] DEFAULT (newid()),
[start_time] [datetime] NULL,
[database_name] [nvarchar] (128) COLLATE Chinese_PRC_CI_AS NULL,
[message] [nvarchar] (max) COLLATE Chinese_PRC_CI_AS NULL,
[sql_text] [nvarchar] (max) COLLATE Chinese_PRC_CI_AS NULL,
[alltext] [nvarchar] (max) COLLATE Chinese_PRC_CI_AS NULL,
-- [worker_address] [nvarchar] (1000) COLLATE Chinese_PRC_CI_AS NULL,
[username] [nvarchar] (1000) COLLATE Chinese_PRC_CI_AS NULL,
[client_hostname] [nvarchar] (1000) COLLATE Chinese_PRC_CI_AS NULL,
[client_app_name] [nvarchar] (1000) COLLATE Chinese_PRC_CI_AS NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
END
GO
-- 建立儲存過程
CREATE PROCEDURE [dbo].[event_error_monitor]
AS
IF NOT EXISTS( SELECT 1 FROM sys.dm_xe_sessions dxs(NOLOCK) WHERE name = 'event_error_monitor') -- 不存在就建立EVENT
-- 建立擴充套件事件,並把資料放入記憶體中
BEGIN
CREATE EVENT session event_error_monitor on server
ADD EVENT sqlserver.error_reported -- error_reported擴充套件事件
(
ACTION -- 返回結果
(
sqlserver.session_id, -- 會話id
sqlserver.plan_handle, -- 計劃控制代碼,可用於檢索圖形計劃
sqlserver.tsql_stack, -- T-SQ堆疊資訊
package0.callstack, -- 當前呼叫堆疊
sqlserver.sql_text, -- 遇到錯誤的SQL查詢
sqlserver.username, -- 使用者名稱
sqlserver.client_app_name, -- 客戶端應用程式名稱
sqlserver.client_hostname, -- 客戶端主機名
-- sqlos.worker_address, -- 當前任務執行時間
sqlserver.database_name -- 當前資料庫名稱
)
WHERE severity >= 11 AND Severity <=16 -- 指定使用者級錯誤
)
ADD TARGET package0.ring_buffer -- 臨時放入記憶體中
WITH (max_dispatch_latency=1seconds)
-- 啟動監控事件
ALTER EVENT SESSION event_error_monitor on server state = START
END
ELSE
-- 儲存過程已經存在就把資料插入表中
BEGIN
-- 將記憶體中已經收集到的錯誤資訊轉存到臨時表中(方便處理)
SELECT
DATEADD(hh,
DATEDIFF(hh, GETUTCDATE(), CURRENT_TIMESTAMP),
n.value('(event/@timestamp)[1]', 'datetime2')) AS [timestamp],
n.value('(event/action[@name="database_name"]/value)[1]', 'nvarchar(128)') AS [database_name],
n.value('(event/action[@name="sql_text"]/value)[1]', 'nvarchar(max)') AS [sql_text],
n.value('(event/data[@name="message"]/value)[1]', 'nvarchar(max)') AS [message],
n.value('(event/action[@name="username"]/value)[1]', 'nvarchar(max)') AS [username],
n.value('(event/action[@name="client_hostname"]/value)[1]', 'nvarchar(max)') AS [client_hostname],
n.value('(event/action[@name="client_app_name"]/value)[1]', 'nvarchar(max)') AS [client_app_name],
n.value('(event/action[@name="tsql_stack"]/value/frames/frame/@handle)[1]', 'varchar(max)') AS [tsql_stack],
n.value('(event/action[@name="tsql_stack"]/value/frames/frame/@offsetStart)[1]', 'int') AS [statement_start_offset],
n.value('(event/action[@name="tsql_stack"]/value/frames/frame/@offsetEnd)[1]', 'int') AS [statement_end_offset]
into #error_monitor -- 臨時表
FROM
( SELECT td.query('.') as n
FROM
(
SELECT CAST(target_data AS XML) as target_data
FROM sys.dm_xe_sessions AS s
JOIN sys.dm_xe_session_targets AS t
ON t.event_session_address = s.address
WHERE s.name = 'event_error_monitor'
--AND t.target_name = 'ring_buffer'
) AS sub
CROSS APPLY target_data.nodes('RingBufferTarget/event') AS q(td)
) as TAB
-- 把資料儲存到自己新建的表中(有SQL語句的直接插入到表中)
INSERT INTO log_error_message(start_time,database_name,message,sql_text,alltext,username,client_hostname,client_app_name)
SELECT TIMESTAMP,database_name,[message],sql_text,'',username,client_hostname,client_app_name
FROM #error_monitor as a
WHERE a.sql_text != '' --AND client_app_name !='Microsoft SQL Server Management Studio - 查詢'
AND a.MESSAGE NOT LIKE '找不到會話控制代碼%' AND a.MESSAGE NOT LIKE '%SqlQueryNotification%' --排除server broker
AND a.MESSAGE NOT LIKE '遠端服務已刪除%'
-- 插入應用執行資訊(沒有SQL的語句通過控制代碼查詢下SQL)
INSERT INTO log_error_message(start_time,database_name,message,sql_text,alltext,username,client_hostname,client_app_name)
SELECT TIMESTAMP,database_name,[message],
SUBSTRING(qt.text,a.statement_start_offset/2+1,
(case when a.statement_end_offset = -1
then DATALENGTH(qt.text)
else a.statement_end_offset end -a.statement_start_offset)/2 + 1) sql_text,qt.text alltext,
username,client_hostname,client_app_name
FROM #error_monitor as a
CROSS APPLY sys.dm_exec_sql_text(CONVERT(VARBINARY(max),a.tsql_stack,1)) qt -- 通過控制代碼查詢具體的SQL語句
WHERE a.sql_text IS NULL AND tsql_stack != '' --AND client_app_name = '.Net SqlClient Data Provider'
DROP TABLE #error_monitor -- 刪除臨時表
--重啟清空
ALTER EVENT SESSION event_error_monitor ON SERVER STATE = STOP
ALTER EVENT SESSION event_error_monitor on server state = START
END
-- 美化版預警郵箱
DECLARE @body_html VARCHAR(max)
set @body_html = '<table style="width:100%" cellspacing="0"><tr><td colspan="6" align="center" style="font-weight:bold;color:red">資料庫錯誤監控</td></tr>'
set @body_html = @body_html + '<tr style="text-align: left;"><th>執行時間</th><th>資料庫</th><th>發生錯誤的SQL語句</th><th>訊息</th><th>使用者名稱</th><th>應用</th><th>應用程式名</th></tr>'
-- 格式處理(沒內容就空格填充)
select @body_html = @body_html + '<tr><td>'
+ case (isnull(start_time, '')) when '' then ' ' else convert(varchar(20), start_time, 120) end + '</td><td>'
+ case (isnull(database_name, '')) when '' then ' ' else database_name end + '</td><td>'
+ case (isnull(sql_text, '')) when '' then ' ' else sql_text end + '</td><td>'
+ case (isnull(message, '')) when '' then ' ' else message end + '</td><td>'
+ case (isnull(username, '')) when '' then ' ' else username end + '</td><td>'
+ case (isnull(client_hostname, '')) when '' then ' ' else client_hostname end + '</td><td>'
+ case (isnull(client_app_name, '')) when '' then ' ' else client_app_name end + '</td></tr>'
from (
select start_time, database_name,sql_text, message, username, client_hostname, client_app_name
from [dbo].[log_error_message]
where start_time >= dateadd(hh,-2,getdate()) -- 當前時間 - 定時任務的時間間隔(2h)
and client_app_name != 'Microsoft SQL Server Management Studio - 查詢' -- and client_hostname in('')
) as temp_message
set @body_html= @body_html+'</table>'
-- 傳送警告郵件
exec msdb.dbo.sp_send_dbmail
@profile_name = 'SQLServer_DotNetCrazy', --配置名稱
@recipients = '[email protected]', --收件郵箱
@body_format = 'HTML', --內容格式
@subject = '資料庫監控通知', --文章標題
@body = @body_html --郵件內容
go
下節預估:定時任務、完整版監控
PS:估計先得更八字的文章(拖太久)然後更完SQLServer更MySQL,等MySQL監控更完會說下備份與恢復,接著我們開架構篇(MyCat系列先不講放在Redis和爬蟲系列的後面)
晚點在下面補上