【译】The Accidental DBA:Troubleshooting

最近重新翻看The Accidental DBA,将Troubleshooting部分稍作整理,方便以后查阅。此篇是Part 3
Part 1:The Accidental DBA:SQL Server Backup
Part 2:The Accidental DBA:Troubleshooting Performance
一、Wait Statistics Analysis
当SQL Server执行一项任务,如果它需要等待(页面释放锁、从磁盘读取页面到内存、事务日志写入),SQL Server会记录等待类型和等待时间。这些信息累积的,可以从sys.dm_os_wait_stats中查询。
如果你还没有使用过Wait Statistics,推荐你先阅读Paul’s wait stats post以及SQL Server Performance Tuning Using Wait Statistics: A Beginners Guide白皮书。
1.1、Viewing Wait Statistics
如果你执行下面语句

SELECT *
FROM sys.dm_os_wait_stats
ORDER BY wait_time_ms DESC
View Code

你将得到类似下图结果

这个结果并没有太大的用处,我们需要过滤掉那些不相干的等待。我使用Paul的代码

WITH [Waits] AS
    (SELECT
        [wait_type],
        [wait_time_ms] / 1000.0 AS [WaitS],
        ([wait_time_ms] - [signal_wait_time_ms]) / 1000.0 AS [ResourceS],
        [signal_wait_time_ms] / 1000.0 AS [SignalS],
        [waiting_tasks_count] AS [WaitCount],
        100.0 * [wait_time_ms] / SUM ([wait_time_ms]) OVER() AS [Percentage],
        ROW_NUMBER() OVER(ORDER BY [wait_time_ms] DESC) AS [RowNum]
    FROM sys.dm_os_wait_stats
    WHERE [wait_type] NOT IN (
        N'BROKER_EVENTHANDLER', N'BROKER_RECEIVE_WAITFOR',
        N'BROKER_TASK_STOP', N'BROKER_TO_FLUSH',
        N'BROKER_TRANSMITTER', N'CHECKPOINT_QUEUE',
        N'CHKPT', N'CLR_AUTO_EVENT',
        N'CLR_MANUAL_EVENT', N'CLR_SEMAPHORE',
 
        -- Maybe uncomment these four if you have mirroring issues
        N'DBMIRROR_DBM_EVENT', N'DBMIRROR_EVENTS_QUEUE',
        N'DBMIRROR_WORKER_QUEUE', N'DBMIRRORING_CMD',
 
        N'DIRTY_PAGE_POLL', N'DISPATCHER_QUEUE_SEMAPHORE',
        N'EXECSYNC', N'FSAGENT',
        N'FT_IFTS_SCHEDULER_IDLE_WAIT', N'FT_IFTSHC_MUTEX',
 
        -- Maybe uncomment these six if you have AG issues
        N'HADR_CLUSAPI_CALL', N'HADR_FILESTREAM_IOMGR_IOCOMPLETION',
        N'HADR_LOGCAPTURE_WAIT', N'HADR_NOTIFICATION_DEQUEUE',
        N'HADR_TIMER_TASK', N'HADR_WORK_QUEUE',
 
        N'KSOURCE_WAKEUP', N'LAZYWRITER_SLEEP',
        N'LOGMGR_QUEUE', N'MEMORY_ALLOCATION_EXT',
        N'ONDEMAND_TASK_QUEUE',
        N'PREEMPTIVE_XE_GETTARGETSTATE',
        N'PWAIT_ALL_COMPONENTS_INITIALIZED',
        N'PWAIT_DIRECTLOGCONSUMER_GETNEXT',
        N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', N'QDS_ASYNC_QUEUE',
        N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP',
        N'QDS_SHUTDOWN_QUEUE', N'REDO_THREAD_PENDING_WORK',
        N'REQUEST_FOR_DEADLOCK_SEARCH', N'RESOURCE_QUEUE',
        N'SERVER_IDLE_CHECK', N'SLEEP_BPOOL_FLUSH',
        N'SLEEP_DBSTARTUP', N'SLEEP_DCOMSTARTUP',
        N'SLEEP_MASTERDBREADY', N'SLEEP_MASTERMDREADY',
        N'SLEEP_MASTERUPGRADED', N'SLEEP_MSDBSTARTUP',
        N'SLEEP_SYSTEMTASK', N'SLEEP_TASK',
        N'SLEEP_TEMPDBSTARTUP', N'SNI_HTTP_ACCEPT',
        N'SP_SERVER_DIAGNOSTICS_SLEEP', N'SQLTRACE_BUFFER_FLUSH',
        N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP',
        N'SQLTRACE_WAIT_ENTRIES', N'WAIT_FOR_RESULTS',
        N'WAITFOR', N'WAITFOR_TASKSHUTDOWN',
        N'WAIT_XTP_RECOVERY',
        N'WAIT_XTP_HOST_WAIT', N'WAIT_XTP_OFFLINE_CKPT_NEW_LOG',
        N'WAIT_XTP_CKPT_CLOSE', N'XE_DISPATCHER_JOIN',
        N'XE_DISPATCHER_WAIT', N'XE_TIMER_EVENT')
    AND [waiting_tasks_count] > 0
    )
SELECT
    MAX ([W1].[wait_type]) AS [WaitType],
    CAST (MAX ([W1].[WaitS]) AS DECIMAL (16,2)) AS [Wait_S],
    CAST (MAX ([W1].[ResourceS]) AS DECIMAL (16,2)) AS [Resource_S],
    CAST (MAX ([W1].[SignalS]) AS DECIMAL (16,2)) AS [Signal_S],
    MAX ([W1].[WaitCount]) AS [WaitCount],
    CAST (MAX ([W1].[Percentage]) AS DECIMAL (5,2)) AS [Percentage],
    CAST ((MAX ([W1].[WaitS]) / MAX ([W1].[WaitCount])) AS DECIMAL (16,4)) AS [AvgWait_S],
    CAST ((MAX ([W1].[ResourceS]) / MAX ([W1].[WaitCount])) AS DECIMAL (16,4)) AS [AvgRes_S],
    CAST ((MAX ([W1].[SignalS]) / MAX ([W1].[WaitCount])) AS DECIMAL (16,4)) AS [AvgSig_S],
    CAST ('https://www.sqlskills.com/help/waits/' + MAX ([W1].[wait_type]) as XML) AS [Help/Info URL]
FROM [Waits] AS [W1]
INNER JOIN [Waits] AS [W2]
    ON [W2].[RowNum] <= [W1].[RowNum]
GROUP BY [W1].[RowNum]
HAVING SUM ([W2].[Percentage]) - MAX( [W1].[Percentage] ) < 95; -- percentage threshold
GO
View Code


等待类型可以参考MSDN条目中的sys.dm_os_wait_stats
注意百分比的计算,它是使用各等待类型的wait_time_ms除以"总的"wait_time_ms,而这个"总的"是指查询语句中没有过滤掉的那些等待。因此,如果你修改过滤类型,百分比会跟着变动。
1.2、Capturing Wait Statistics
到目前为止,我已经讨论过在某个时间点查看等待统计。作为一名DBA,你需要知道每个实例上哪些等待是正常,并且在系统性能不佳的时候使用这些值。
捕获等待统计信息的最简单的方法是定期将快照数据保存到数据表中;你可以在文章Capturing Baselines for SQL Server: Wait Statistics中找到对应的语句。一旦你有方法来捕获数据,定期回顾它以了解典型等待,并找出潜在的问题。当你发现问题时,你可以使用等待统计信息来帮助你进行故障排除。
1.3、Using the Data
当你发现问题时,一个推荐的做法是,运行你的等待统计查询,然后将结果和你的基线对比。如果你看出什么不一样,你就知道从哪里开始你的调查。但是,等待统计信息仅仅告诉你从哪里开始查找你的答案。Do not assume that your highest wait is the problem, or even that it’s a problem at all.
例如,一个常见的等待CXPACKET,CXPACKET意味着使用并行。如果它是靠前的等待类型,是不是意味着你应该马上调整实例的MAXDOP设置?不是,你应该先去分析为什么它是最高的等待,如果缺少索引并且有大量的表扫描,那么将会有CXPACKET等待。因此你应该优化语句,而不是调整MAXDOP。
另一个例子是WRITELOG等待,当SQL Server等待日志刷新时会产生WRITELOG等待,当信息需要被写入到数据库事务日志时会产生日志刷新。日志刷新应该迅速完成,because when there is a delay in a log write, then the task that initiated the modification has to wait, and tasks may be waiting behind that。但是,日志刷新并不是每一次都发生,所以你会有WRITELOG等待。如果你看到WRITELOG等待是靠前的,不要认为你需要新的存储,你需要进一步去调查。你可以去检查读写情况。
你可以从SQL Server Wait Types and Latch Classes library得到各类等待的详细信息。
正如你所看到的两个例子,等待统计信息是一个起点。它们非常有价值,很容易把它们当作"答案",但它们不是。Wait statistics do not tell you the entire story about a SQL Server implementation.There is no one “thing” that tells you the entire story, which is why troubleshooting can be incredibly frustrating, yet wonderfully satisfying when you find the root of a problem.
成功地解决SQL Server中的性能问题需要了解所有可用的数据,以帮助你发现和调查,了解从哪里开始,以及捕获哪些信息。
Wait Statistics以往文章:http://www.cnblogs.com/Uest/p/5763100.html#waitstats
二、Monitoring Disk I/O
数据库存储看起来像是一个黑盒子,DBA负责管理数据库,而这些数据库通常位于SAN的某个地方,space simply presented to a DBA as a drive letter representing some amount of space.But storage is about more than a drive letter and a few hundred GBs.拥有足够的空间对数据库很重要,但我经常看到客户计划的是容量而不是性能,这可能会成为一个问题。作为一名DBA,你不仅需要向存储管理员询问存储空间,还需要询问吞吐量,and the best way to back up your request is with data.
2.1、I/O Data in SQL Server
在Accidental DBA系列,我已经提到了不少DMV,今天也不例外。如果你想查看SQL Server中的I/O情况,你可以使用sys.dm_io_virtual_file_stats动态管理视图。2005版本之前的你可以使用fn_virtualfilestats函数得到相同的信息。Paul的文章中有一个我经常用来获取文件信息的查询How to examine IO subsystem latencies from within SQL Server
使用下面语句返回

SELECT
    [ReadLatency] =
        CASE WHEN [num_of_reads] = 0
            THEN 0 ELSE ([io_stall_read_ms] / [num_of_reads]) END,
    [WriteLatency] =
        CASE WHEN [num_of_writes] = 0
            THEN 0 ELSE ([io_stall_write_ms] / [num_of_writes]) END,
    [Latency] =
        CASE WHEN ([num_of_reads] = 0 AND [num_of_writes] = 0)
            THEN 0 ELSE ([io_stall] / ([num_of_reads] + [num_of_writes])) END,
    [AvgBPerRead] =
        CASE WHEN [num_of_reads] = 0
            THEN 0 ELSE ([num_of_bytes_read] / [num_of_reads]) END,
    [AvgBPerWrite] =
        CASE WHEN [num_of_writes] = 0
            THEN 0 ELSE ([num_of_bytes_written] / [num_of_writes]) END,
    [AvgBPerTransfer] =
        CASE WHEN ([num_of_reads] = 0 AND [num_of_writes] = 0)
            THEN 0 ELSE
                (([num_of_bytes_read] + [num_of_bytes_written]) /
                ([num_of_reads] + [num_of_writes])) END,
    LEFT ([mf].[physical_name], 2) AS [Drive],
    DB_NAME ([vfs].[database_id]) AS [DB],
    [mf].[physical_name]
FROM
    sys.dm_io_virtual_file_stats (NULL,NULL) AS [vfs]
JOIN sys.master_files AS [mf]
    ON [vfs].[database_id] = [mf].[database_id]
    AND [vfs].[file_id] = [mf].[file_id]
-- WHERE [vfs].[file_id] = 2 -- log files
-- ORDER BY [Latency] DESC
-- ORDER BY [ReadLatency] DESC
ORDER BY [WriteLatency] DESC;
GO
View Code


数据看起来好像存在严惩的磁盘问题——写延迟超过1s,尤其是我折笔记本电脑使用的SSD。我使用这张截图是为了说明这些数据是累积的,只有当实例重启才会重置。你可以启动一个大IO操作(例如重建索引)来影响sys.dm_io_virtual_file_stats中的数值,并且它可能需要点时间才能恢复正常。Joe有一篇文章详细讨论这个问题,Avoid false negatives when comparing sys.dm_io_virtual_file_stats data to perfmon counter data.
sys.dm_io_virtual_file_stats动态管理视图不仅显示延迟,它还会告诉你哪些文件有最高的读和写以及读写的量。你可以确定哪些数据库(和文件)是重点访问的,并随着时间的推移来确定它(们)是否发生了变化。
2.2、I/O Data in Windows
如果你想收集Windows的I/O数据,性能监视器是最好的选择,我喜欢查看每个磁盘的以下计数器信息:
Avg. Disk sec/Read
Avg. Disk Bytes/Read
Avg. Disk sec/Write
Avg. Disk Bytes/Write
Jon谈论过性能计数器,上面提到的计数器会告诉你延迟和吞吐量。延迟是指一个I/O请求需要花费多长时间,但是对于不同的人员它的测量方法会有所不同。通常我们关心的是来自SQL Server的延迟。对于Windows,延迟是从初始I/O请求到完成请求的这段时间。正如,Joe在他的文章中提到,你可能会在从SQL Server与Windows的延迟之间看到一些不同。
当我们使用性能监视器测量延迟,我们查看Avg Disk sec/Read和Avg Disk sec/Write.磁盘缓存、磁盘、控制器卡或存储系统,影响读取和写入值。写入通常被写入到缓存中,并且应该很快完成。读取,当不在缓存中,必须到磁盘获取(这通常会花费更长的时间)。很容易认为延迟和磁盘完全相关,但事实并非如此。记住,我们在讨论I/O子系统,这包括从服务器本身到磁盘并且返回的整个路径。这个路径包括服务器中的HBAs、交换机、SAN中的控制器、SAN中的缓存和磁盘本身。You can never assume that latency is high because the disks can’t keep up. 有时,HBAs的队列深度设置过低,或者你可能与一个失败的组件(比如GBIC)或一个坏的端口卡有间歇性的连接。你必须将你所拥有的延迟信息与存储团队共享,并请求他们进行调查。希望你有一个精明的存储团队,知道如何调查所有的路径。在复杂的环境中,一张图片胜过千言万语。最好是和存储管理员绘制出从操作系统到SAN LUN或卷的映射。这将会有关于服务器、到SAN的路径和SAN本身的讨论。
Remember what matters is getting the I/O to the application. If the IO leaves the disk but gets stuck along the way, that adds to latency. There could be an alternate path available (multi-pathing), but maybe not.
使用Avg. Disk Bytes/Read和Avg. Disk Bytes/Write测量吞吐量,会告诉我们服务器和存储之间转移了多少数据。这是有价值的,而且常常比计算I/Os更有用,因为我们可以用它来了解我们的磁盘需要读写多少数据才能满足需求。理想情况下,当系统进行优化时,你会捕获这些信息(比如添加索引来减少全表扫描,这样会影响I/O的数量),but often times you will need to just work within the current configuration.
2.3、Capturing Baselines
在讨论sys.dm_os_virtual_file_stats动态管理视图时,我提到了基线。如果你认为我将离开它,那么你一定不知道我对基线的爱!
你希望从SQL Server和Windows获取数据,以向存储管理员提供吞吐量数据。你需要这些数据在SAN上进行存储,这不仅可以给你足够的空间来容纳预期的数据库增长,而且还将为你提供数据库所需的IOPs和MB/sec.
除了单次I/O和延迟数据查看,你应该设立一个进程定期收集这些数据,那样你就能识别是否有变更。You will want to know if a database suddenly starts issuing more IOs (did someone drop an index?) or if the change is I/Os is gradual.你需要确保I/Os在你期望的时间内完成。记住,SAN是一个共享存储,你并不可能总是知道谁和你共享那个存储。如果另一个具有高I/O需求的应用程序被放置在相同的磁盘组上,并且你的延迟增加,那么你希望能够精确地指出这种变化并向你的SAN管理员提供支持,从而支持在你的数据库中性能的变化。
2.4、总结
作为一名DBA,你需要了解你的数据库在读写方面的表现,并且最好了解你的存储团队。同样,了解数据库真正"生存",和哪些应用共享相同的存储。当一个性能问题出现时,使用基线数据作为开始,并且不要犹豫让你的SAN管理员获取更多信息。虽然有很多数据可以供DBA使用,但你无法自己获取完整的图片。给存储团队买些披萨或甜甜圈,交一些新朋友,这可能没什么坏处。最后如果你有兴趣深入挖掘SQL Server I/O的细节,你可以阅读下面链接:
How It Works: Bob Dorr’s SQL Server I/O Presentation
SQL Server 2000 I/O Basics (still relevant)
SQL Server I/O Basics, Chapter 2
三、Tempdb Contention
Tempdb contention refers to a bottleneck for threads trying to access allocation pages that are in-memory; it has nothing to do with I/O.
考虑这样的场景,数百个并发查询,这些查询创建、使用、然后删除临时表。每次创建一个临时表,必须分配一个数据页,再加上一个分配元数据页来跟踪分配给该表的数据页。这就需要在PFS页上做标记以表明那两页已被分配。当临时表被删除时,这些页被收回,它们要再次在PFS页上标记。每次只有一个线程可以更改分配页面,这样会使其成为热点,并降低总体工作负载。
但真正有趣的是许多人没有意识到他们有这个问题——甚至是经验丰富的DBA。使用dm_os_waiting_tasks动态管理视图很容易得出你是否有这样的问题。如果你运行下面的查询,你将了解服务器上的各种线程正在等待什么

SELECT
    [owt].[session_id],
    [owt].[exec_context_id],
    [owt].[wait_duration_ms],
    [owt].[wait_type],
    [owt].[blocking_session_id],
    [owt].[resource_description],
    CASE [owt].[wait_type]
        WHEN N'CXPACKET' THEN
            RIGHT ([owt].[resource_description],
            CHARINDEX (N'=', REVERSE ([owt].[resource_description])) - 1)
        ELSE NULL
    END AS [Node ID],
    [es].[program_name],
    [est].text,
    [er].[database_id],
    [eqp].[query_plan],
    [er].[cpu_time]
FROM sys.dm_os_waiting_tasks [owt]
INNER JOIN sys.dm_exec_sessions [es] ON
    [owt].[session_id] = [es].[session_id]
INNER JOIN sys.dm_exec_requests [er] ON
    [es].[session_id] = [er].[session_id]
OUTER APPLY sys.dm_exec_sql_text ([er].[sql_handle]) [est]
OUTER APPLY sys.dm_exec_query_plan ([er].[plan_handle]) [eqp]
WHERE
    [es].[is_user_process] = 1
ORDER BY
    [owt].[session_id],
    [owt].[exec_context_id]
GO
waiting tasks

如果你看到很多wait_type列是PAGELATCH_UP或者PAGELATCH_EX,并且resource_description是2:1:1,那么这是PFS页(dbid=2,fileid=1,pageid=1),如果resource_description是2:1:3,它是SGAM页。
本节后续部分与原文不同,使用下述脚本模拟tempdb争用

--step1:存储过程创建、使用、删除临时表
CREATE DATABASE demo ;
GO
-- Create stored procedure that creates a temp table, a clustered index and populates with 10 rows
-- The script expects a database called Demo to exist
USE demo ;
GO
CREATE PROCEDURE [dbo].[usp_temp_table]
AS 
    CREATE TABLE #tmpTable
        (
          c1 INT,
          c2 INT,
          c3 CHAR(5000)
        ) ;
    CREATE UNIQUE CLUSTERED INDEX cix_c1 ON #tmptable ( c1 ) ;
    DECLARE @i INT = 0 ;
    WHILE ( @i < 10 ) 
        BEGIN
            INSERT  INTO #tmpTable ( c1, c2, c3 )
            VALUES  ( @i, @i + 100, 'coeo' ) ;
            SET  @i += 1 ;
        END ;
    DROP TABLE #tmpTable;
GO
-- Create stored procedure that runs usp_temp_table 50 times
CREATE PROCEDURE [dbo].[usp_loop_temp_table]
AS 
    SET nocount ON ;
    DECLARE @i INT = 0 ;
    WHILE ( @i < 100 )
        BEGIN
            EXEC demo.dbo.usp_temp_table ;
            SET  @i += 1 ;
        END ;
GO        

--step2:清除sys.dm_os_wait_stats信息
DBCC sqlperf('sys.dm_os_wait_stats',clear);

--step3:cmd命令行中执行,Ostress测试300个线程并发,每个线程执行1次
shell> d:
shell> cd D:\Program Files\Microsoft Corporation\RMLUtils\
shell> Ostress -S"127.0.0.1,7777" -E -Q"EXEC demo.dbo.usp_loop_temp_table;" -o"E:\Ostress\out" -n300 -r1

--step4:step3完成马上执行
SELECT *
FROM   sys.dm_os_wait_stats
WHERE wait_type LIKE 'PAGELATCH%'
ORDER BY
       wait_time_ms DESC;
tempdb contention

在执行step3时,可以从sys.dm_os_waiting_tasks中查看当前等待的资源

3.1、缓解方案
你可以从下面几个方面来缓解这种争用,并提升总体工作负载的吞吐量:
1、停止使用临时表
2、启动参数中添加1118跟踪标记
3、tempdb使用多个数据文件
方案一、说来容易做来难,但它能解决这个问题。认真想想,你可能会发现在你的环境中临时表是一种设计模式,因为它们曾经让一个查询更快,然后每个人都开始使用它们,而不管它们是否"真的"能提升性能和吞吐量。这是另一个话题,不在这篇文章的范围之内。
方案二、通过稍微改变使用的分配算法来防止SGAM页面的争用。启用这个功能没有任何缺点,我甚至说世界各地的SQL Server实例应该默认启用该跟踪标志
方案三、通过将分配工作负载分布在多个文件上,从而减少对单个文件PFS页面的争用,有助于消除PFS页面争用。但是你应该创建多少个数据文件呢?
首先计算出你的逻辑处理器核数(例如,2个CPU,每个CPU有4个物理核心,如果启用超线程,那么将有2cpu*4cores*2hyperthreading=16logical cores)

--logical cores and tempdb data files
SELECT os.Cores, df.Files
FROM
   (SELECT COUNT(*) AS Cores FROM sys.dm_os_schedulers WHERE status = 'VISIBLE ONLINE') AS os,
   (SELECT COUNT(*) AS Files FROM tempdb.sys.database_files WHERE type_desc = 'ROWS') AS df;
GO
logical cores

如果小于等于8个logical cores,则有多少个逻辑核数就创建多少个数据文件,如果大于8个logical cores,则创建8个数据文件,如果还存在PFS争用,就再添加4个数据文件(不要超过logical cores)。确保所有的tempdb数据文件有相同的大小。这条建议可以参考微软官方文章
方案四、重用临时对象(Temporary Object Reuse)

Beginning with SQL Server 2005, it’s possible for SQL Server to cache temporary object definitions so that they can be reused if the same object needs to be created again. To be more specific, one IAM page (Index Allocation Map) and one extent are cached.
Objects that are reused don’t have to be allocated new space and therefore won’t contribute to any allocation problems. Optimizing your code to ensure that your temporary tables are being cached will help to reduce any potential problems.

SQL Server默认尝试缓存临时表,使用以下语句检查数据库是否有缓存你所创建的临时表

SET NOCOUNT ON ;
GO
DECLARE @table_counter_before_test BIGINT ;
SELECT  @table_counter_before_test = cntr_value
FROM    sys.dm_os_performance_counters
WHERE   counter_name = 'Temp Tables Creation Rate' ;
DECLARE @i INT = 0 ;
WHILE ( @i < 10 ) 
    BEGIN
        EXEC demo.dbo.usp_loop_temp_table ;
        SELECT  @i += 1 ;
    END ;
DECLARE @table_counter_after_test BIGINT ;
SELECT  @table_counter_after_test = cntr_value
FROM    sys.dm_os_performance_counters
WHERE   counter_name = 'Temp Tables Creation Rate' ;
PRINT 'Temp tables created during the test: '
    + CONVERT(VARCHAR(100), @table_counter_after_test
    - @table_counter_before_test) ;
View Code

只需把中间的存储过程名称修改为你自己的即可~
3.2、方案效果
执行tempdb contention代码得到的结果


测试方案三和方案四对tempdb争用的影响

/**** 方案三 tempdb添加多个数据文件 ****/
--添加数据文件
USE [master]
GO
ALTER DATABASE [tempdb] ADD FILE ( 
    NAME = N'tempdev1', 
    FILENAME = N'D:\SQL08R2\MSSQL10_50.SQL08R2\MSSQL\DATA\tempdev1.ndf' , 
    SIZE = 8192KB , 
    FILEGROWTH = 10%)
GO
ALTER DATABASE [tempdb] ADD FILE ( 
    NAME = N'tempdev2', 
    FILENAME = N'D:\SQL08R2\MSSQL10_50.SQL08R2\MSSQL\DATA\tempdev2.ndf' , 
    SIZE = 8192KB , 
    FILEGROWTH = 10%)
GO
ALTER DATABASE [tempdb] ADD FILE ( 
    NAME = N'tempdev3', 
    FILENAME = N'D:\SQL08R2\MSSQL10_50.SQL08R2\MSSQL\DATA\tempdev3.ndf' , 
    SIZE = 8192KB , 
    FILEGROWTH = 10%)
GO

/**** 方案四 CREATE INDEX语句放到表定义内,重用临时表 ****/
-- Create stored procedure that creates a temp table, a clustered index and populates with 10 rows
-- The script expects a database called Demo to exist
USE demo ;
GO
ALTER PROCEDURE [dbo].[usp_temp_table]
AS 
    CREATE TABLE #tmpTable
        (
          c1 INT UNIQUE CLUSTERED,
          c2 INT,
          c3 CHAR(5000)
        ) ;
    --CREATE UNIQUE CLUSTERED INDEX cix_c1 ON #tmptable ( c1 ) ;
    DECLARE @i INT = 0 ;
    WHILE ( @i < 10 ) 
        BEGIN
            INSERT  INTO #tmpTable ( c1, c2, c3 )
            VALUES  ( @i, @i + 100, 'coeo' ) ;
            SET  @i += 1 ;
        END ;
    DROP TABLE #tmpTable;
GO
View Code



step4得到的sys.dm_os_wait_stats汇总如下:

  调整前 应用方案三+方案四后
持续时间 2分13秒 58秒
等待PAGELATCH的总任务数 3433286 748184
等待PAGELATCH的总时间 26194秒 10438秒
等待PAGELATCH的最长单个任务等待时间 1078毫秒 812毫秒

3.3 、疑问
测试过程中当并发数设置较高时(300+),普通连接无法打开,ERRORLOG中
New queries assigned to process on Node 0 have not been picked up by a worker thread in the last 60 seconds. Blocking or long-running queries can contribute to this condition, and may degrade client response time. Use the "max worker threads" configuration option to increase number of allowable threads, or optimize current running queries. SQL Process Utilization: 36%. System Idle: 56%.
尝试将max worker threads改大,依旧不起效果。需要通过DAC查看sys.dm_os_waiting_tasks信息(普通连接会卡住)

--DAC
Dedicated admin connection support was established for listening locally on port ****. 
tcp:127.0.0.1,50978

--worker thread 的总数
select COUNT(*)from sys.dm_os_workers

--自动配置,max worker threads
select max_workers_count from sys.dm_os_sys_info

select * from sys.configurations c where name like '%max worker threads%'

select scheduler_id,status,current_workers_count from sys.dm_os_schedulers
SELECT SUM(current_workers_count) FROM sys.dm_os_schedulers

--重新初始化errorlog
sp_cycle_errorlog
dbcc errorlog


/**** Clean Up ****/
--删除数据库
DROP DATABASE demo

--删除数据文件
USE [tempdb]
GO
ALTER DATABASE [tempdb]  REMOVE FILE [tempdev1]
GO
USE [tempdb]
GO
ALTER DATABASE [tempdb]  REMOVE FILE [tempdev2]
GO
USE [tempdb]
GO
ALTER DATABASE [tempdb]  REMOVE FILE [tempdev3]
GO
View Code

Ostress是RML Utilities的一个组件(当然也可以使用SQLQueryStress测试并发),详细内容可参考《Professional SQL Server 2008 Internals and Troubleshooting》的Chapter 12: Introducing RML Utilities for Stress Testing and Trace File Analysis
本文只是概述tempdb的争用,但正如我所说,这是很常见的。你可以从下面的链接获得更多信息:
Misconceptions around TF 1118
Tempdb configuration survey results and advice
SQLskills SQL101: Trace Flags文中有提及Trace Flag 1118
四、Blocking
SQL Server使用锁来做各种各样的事,从保护和隔离资源到显示某人正在使用数据库或访问表。许多锁与其他锁是兼容的,它并不总是限制一个资源只适用一个会话。但是,当锁不兼容时就会发生阻塞。如果阻塞很快完成,那么被阻塞者甚至没有意识到它们被暂时阻塞了。一个正常的系统总会有一些阻塞。这是一个自然会发生的。然而,如果有一个长时间运行的事务或者事务影响行数很大,阻塞可能就是一个重要/主要的问题。
4.1、Root Cause Analysis
在许多情况下,根本原因分析将揭示你的环境中的一些低效问题
假设一个更新只需要修改行的子集,但是没有索引来帮助搜索。
假设一个事务开启,修改部分数据,然后等待用户输入。在数据修改时是持有锁的,并且直到事务提交修改相关的锁不会释放。在这种情况下,等待的时间是不确定的。这可能会造成巨大的问题,不仅仅是阻塞,而且还有日志(同时影响恢复)。
我的观点是,锁并不总是导致问题的主要原因,但它经常被指责。相反,锁和阻塞实际上是一些效率低下的症状,进一步的分析将帮助你更好地理解真正的问题所在。
4.2、Analyzing Blocking
当性能很差时有很多项目可以检查。通常,我们推荐从等待统计信息开始。在Erin的博客The Accidental DBA (Day 25 of 30): Wait Statistics Analysis,她提到使用sys.dm_os_wait_stats。经常使用这个并且收集服务器的一般特征的基线将会帮助你在系统运行缓慢或者查看某一时刻是否发生了变化。确保你阅读了Erin的博客以及她提及的链接。The more you know about your server when it’s healthy, the more equipped you’ll be when there’s a problem.
如果你当前正有一个阻塞状况,那么使用sys.dm_os_waiting_tasks动态管理视图。它会告诉你是否有谁被阻塞,以及阻塞者的spid。然而,如果涉及到多个连接(SPIDs),这可能很快变得复杂。有时,找到谁是阻塞的源头就是一个问题。另外,你需要知道更多信息,你可以使用sys.dm_tran_locks。阅读Glenn Berry’s A DMV A Day – Day 27,文章提供了很好的阻塞查询语句。
一旦你知道阻塞的源头SPID,你可以使用各种命令来组合正在发生的事情。我推荐几种别的方式:
1、旧的方法sp_blocker_pss08,你可以从这篇文章得到代码。文章中说此代码仅适用于SQL Server 2000和SQL Server 2005,但是它在SQL Server 2012仍然可以使用。如果你的公司在使用第三方产品时有问题,这个代码可能对你有好处。它很简单,纯TSQL,在阻塞时它会给你很多信息。
2、比较新的方法是使用SQLDiag。它是基于命令行的,相比执行一个存储过程,它需要稍微多一点的准备学习。你当然应该花点时间了解它,但是如果你现在正在尝试解决一个阻塞问题,那么现在就不是学习SQLDiag的时候。
3、最简单的方法是sp_WhoIsActive,它以表格的形式将sp_blocker_pss08的结果组合起来。
And, if you’re trying to see if patterns exist over time, 可以参考Using the Blocked Process Report in SQL Server 2005/2008.Jonathan写了一篇很好的文章,介绍如何处理设置这个。
4.3、The Resolution
最终,你要找出谁最开始阻塞,为什么它们的事务运行那么久?如果是因为低效的查询,你是否可以重写语句?如果是因为低效的执行计划,你是否可以增加索引?如果你在修改大批量的数据,你是否可以拆分成小块?这些总是首先要尝试的东西。
4.3.1、Consider Row Versioning
1、Read Committed using Locking:这是默认设置,没有启用行版本控制选项
2、Statement-level Read Consistency (or, read committed using row versioning):只开启read_commmitted_snapshot数据库选项
3、Transaction-level Read Consistency (or, Snapshot Isolation): 只开启allow_snapshot_isolation数据库选项
4、第四种状态是同时开启read_commmitted_snapshot和allow_snapshot_isolation数据库选项
NOTE: There are numerous places [online] where it’s stated that both of these options are required for versioning; this is incorrect. You can have neither, only one, or both. All four states produce different behaviors.
最终,版本控制可能是你想要的,但是不要在不完全优化你的环境的情况下直接跳到这个问题上。
4.4、Summary
1、Find where you’re waiting most
2、Do root cause analysis to get to the code or SPID that’s causing you grief
3、Analyze the code to see if it can be changed to reduce the time that the locks are held
4、Consider changing isolation
五、Deadlocks
在SQL Server的两个或多个任务中,如果某个任务锁定了其他任务试图锁定的资源,会造成这些任务永久阻塞,从而出现死锁。在SQL Server中,死锁监视器后台任务负责检测和解决死锁问题,如果监视器检测到这种循环依赖关系,会选择其中一个任务作为牺牲器,然后终止其事务并提示错误(1205错误)。在第三方供应商程序,想消除死锁可能并不是那么容易,但是你依旧可以收集死锁信息以帮助第三方供应商分析并有可能得到解决问题的方法。
5.1、Collecting Information
在SQL Server 2008之前,收集死锁信息需要启用跟踪标志、配置SQL跟踪、事件通知或使用VMI警报。跟踪标志1222、1205或者1204将死锁信息以文本形式写入到错误日志(ERRORLOG),SQL跟踪、事件通知和WMI警报可以将死锁收集为XML形式。自从SQL Server 2008引入扩展事件,新的system_health事件会话默认会捕获死锁信息

-- SQL Server 2008 R2
;WITH SystemHealth
AS (
SELECT CAST(target_data as xml) AS TargetData
FROM sys.dm_xe_session_targets st
JOIN sys.dm_xe_sessions s
ON s.address = st.event_session_address
WHERE name = 'system_health'
AND st.target_name = 'ring_buffer')
SELECT XEventData.XEvent.value('@timestamp','datetime')as Creation_Date,CAST(XEventData.XEvent.value('(data/value)[1]','VARCHAR(MAX)') AS XML) AS DeadLockGraph
FROM SystemHealth
CROSS APPLY TargetData.nodes('//RingBufferTarget/event') AS XEventData (XEvent)
WHERE XEventData.XEvent.value('@name','varchar(4000)') = 'xml_deadlock_report'
ORDER BY Creation_Date DESC
View Code

5.2、Analysis
Bart Duncan的死锁分析三步曲,讲述了如何分析1222跟踪标志的输出信息。这些分析方法同样适用于SQL跟踪、事件通知、WMI和扩展事件收集的死锁。死锁图形的格式定义了死锁牺者(deadlock victim),死锁涉及的进程(process-list结点),进程资源信息(resource-list结点)。每一个进程分配processid,用于唯一标识进程,以及进程持有的资源和等待的资源。在每一个进程信息中,executionStack结点显示了死锁语句。
死锁分析的重点领域之一是资源列表(resource-list)部分,它包含所涉及资源的所有信息,以及每个进程所持有和请求的锁类型。它还包含索引和对象名称,或者是对象的分配单元。理解进程之间锁的顺序对死锁处理至关重要。
除了查看死锁的XML或text信息,你还可以图形化查看Graphically Viewing Extended Events Deadlock Graphs。SSMS下的图形不会像XML和text那样详细显示,但是可以用来快速理解死锁类型和锁顺序。在需要深入调查死锁问题时就有必要查看XML或text信息,当然你也可以使用Plan Explorer Pro来查看。
5.3、Possible solutions
有很多潜在的方案可以防止死锁发生,而正确的方案将取决于发生特定死锁的条件。在某些死锁场景,使用覆盖索引可以避免死锁。在另一些场景,可能需要修改隔离级别,或者使用索引提示来强制一个阻塞锁与其他锁不兼容,以防止出现死锁。对死锁的适当分析有助于确定问题的适当解决方案。但是在大多数情况下,在TSQL中简单的错误处理逻辑,或者.net程序代码处理1205错误并尝试重新提交牺牲者事务,可以防止终端用户受到死锁的影响。
5.4、Summary
死锁问题首先得使用一个可用的方法收集死锁信息。SQL Server 2008中的扩展事件默认收集死锁,消除了启用进一步收集的需求,然后等待死锁重新出现以收集信息。
更多死锁收集配置和分析可参考Handling Deadlocks in SQL ServerSQL Server: Deadlock Analysis and Prevention.
阻塞和死锁以往文章:http://www.cnblogs.com/Uest/category/797774.html
六、Transaction Log Growth
我记得几年前的一封电子邮件,大致是说:我有一个10GB的数据文件和一个987GB的日志文件。
导致不受控制的事务日志增长的原因如下:
1、某人创建一个数据库,它的默认恢复模式是完整模式;或者某人将一个数据的恢复模式修改为完整模式
尽管数据库的恢复模式为完整模式,数据库还是表现为简单恢复模式,只要发生checkpoint,事务日志就会被自动截断。这被称为"伪简单恢复模式",这种状态维持到执行完整备份。
2、某人执行了完整备份,将数据库转换成真正的完整恢复模式
数据库完整备份为之后的事务日志备份提供基准,因此只有执行事务日志备份才能截断事务日志
3、没有人执行事务日志备份
由于没人执行事务日志备份,数据库处于完整恢复模式,事务日志不会截断。由于日志不会截断,它最终会填满,然后需要更多的空间来记录更多的日志记录,因此它会自动增长。不停地增长,直到磁盘空间耗尽。并不是要等所有的日志空间填满才进行日志增长,详细参考Log Reservation.
这里的技巧很简单:使用完整(大容量日志)恢复模式的数据库需定期执行日志备份。
只要你知道为什么事务日志不能截断,你可以使用以下语句检查日志不能被截断的原因:

SELECT [name],[log_reuse_wait_desc] FROM sys.databases
GO
View Code

它会罗列出所有数据库最近一次尝试日志清理时不能被截断的原因。在前面的例子,数据库将返回LOG_BACKUP,还有很多其他的原因,包括长时间运行的事务,未复制的事务,活动数据备份等。你可以在联机丛书Factors That Can Delay Log Truncation中找到完整的列表。
不论是哪种原因导致数据库日志增长,选择正确的操作截断事务日志,然后采取预防措施阻止事务日志再次增长。如果你的事务日志已经变得十分大,你可能需要收缩它到一个合适的大小,你可以参考Kimberly的博客获取更多收缩的详细内容。
除了日志增长和潜在的空间耗尽外,日志增长还存在以下问题:
1、每次日志增长,日志的新增部分必须零初始化,这需要花费一些时间,并导致数据库的写操作暂停
2、每次日志增长,日志中就会添加更多的内部数据块(VLF),而VLF越多对日志操作的性能影响就越大
3、当日志变得很大,灾难恢复所需的停机时间会越长。如果整个数据库被损坏,那么需要从备份中重新创建数据和日志文件。日志文件越大,所需要的时间越长(零初始化)
Careful transaction log management is required as part of being responsible for a SQL Server instance, whether as an Accidental or experienced DBA.

posted @ 2017-06-20 08:39  Uest  阅读(544)  评论(0编辑  收藏  举报