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 ='xxx@163.com',                 -- 發件人郵件地址
  @display_name ='SQLServer2014_192.168.36.250', -- 發件人姓名
  @MAILSERVER_NAME = 'smtp.163.com',             -- 郵件服務器地址
  @PORT =25,                                     -- 郵件服務器端口
  @USERNAME = 'xxx@163.com',                     -- 用戶名
  @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 = 'xxx@qq.com',			          --收件郵箱
@body_format = 'HTML',						   --內容格式
@subject = '文章標題',						   --文章標題
@body = '郵件內容<br/><h2>This is Test</h2>...' --郵件內容

效果:

06-10/1.mail.png

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.新建一個會話向導(熟悉后可以直接新建會話)

1.新建會話向導.png

1.新建會話向導2.png

2.設置需要捕獲的擴展事件

2.設置需要捕獲的擴展事件.png

3.這邊捕獲的全局字段和左邊SQL是一樣的(截圖全太麻煩了,所以偷個懶,后面會說怎么生成左邊的核心SQL)

3.捕獲的全局字段.png

4.自己根據服務器性能設置一個合理的值(IO、內存、CPU)

4.設置.png

5.生成核心SQL(我們圖形化的目的就是生成核心SQL,后面可以根據這個SQL自己擴展)

5.生成核心SQL.png

6.核心代碼如下

6.核心代碼.png

7.啟動會話后一個簡單的擴展事件監控就有了

7.啟動會話.png

8.SQLServer提供了查看方式

8.查看.png

9.日志可以自己查下xxx\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\Log

9.日志.png


2.SQL的方式

上面只是過家家,主要目的就是讓大家知道核心SQL是怎么來的,憑什么這么寫

下面就來個制定化監控:

先截圖演示下各個核心點,然后貼一個我封裝的存儲過程附件

1.擴展事件相關的核心代碼

1.擴展事件相關的核心代碼.png

2.內存中數據存儲到臨時表

2.內存中數據存儲到臨時表.png

3.臨時表中的數據存儲到自己建立的表中

我拋一個課后小問給大家:為什么先存儲在臨時表中?(提示:效率)

3.臨時表中的數據存儲到自己建立的表中.png

4.發送監控提醒的郵件

4.發送監控提醒的郵件.png

5.看看數據庫層面多了什么:

5.看看數據庫層面.png

6.來個測試

6.測試.png

7.效果(可以自己美化)

7.效果.png

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 '&nbsp;' else convert(varchar(20), start_time, 120) end + '</td><td>'
        + case (isnull(database_name, '')) when '' then '&nbsp;' else database_name end + '</td><td>'
        + case (isnull(sql_text, '')) when '' then '&nbsp;' else sql_text end + '</td><td>'
        + case (isnull(message, '')) when '' then '&nbsp;' else message end + '</td><td>'
        + case (isnull(username, '')) when '' then '&nbsp;' else username end + '</td><td>'
        + case (isnull(client_hostname, '')) when '' then '&nbsp;' else client_hostname end + '</td><td>'
        + case (isnull(client_app_name, '')) when '' then '&nbsp;' 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 = 'xxxxx@qq.com',                  --收件郵箱
    @body_format = 'HTML',                           --內容格式
    @subject = '數據庫監控通知',                       --文章標題
    @body = @body_html --郵件內容
go

下節預估:定時任務、完整版監控

PS:估計先得更八字的文章(拖太久)然后更完SQLServer更MySQL,等MySQL監控更完會說下備份與恢復,接着我們開架構篇(MyCat系列先不講放在Redis和爬蟲系列的后面)

晚點在下面補上


免責聲明!

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



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