CXPACKET的查询计划等待

时间:2014-01-06 07:12:48

标签: sql-server locking sql-execution-plan

有没有办法找到累积CXPACKET等待的查询计划?

这些等待当前没有进行,我在聚合等待统计数据(dm_os_wait_stats)中看到了它们。

2 个答案:

答案 0 :(得分:0)

等待与进程ID相关联,而不是与查询计划相关联。根据服务器和网络的负载,相同的查询计划将等待不同的资源。

您可以执行以下查询:

SELECT * FROM sys.sysprocesses

查看谁在做什么以及每个过程都涉及等待。​​

阅读完一些文档后,我发现这个有用的帖子: http://gallery.technet.microsoft.com/scriptcenter/23eeb821-2a42-429d-8d94-aee96224701d

它涉及dm_exec_requests视图,其中包含您要查找的wait_type(和时间)。

答案 1 :(得分:0)

我测试了几种不同的方法,使用不同来源的不同脚本并修改它们以满足我们的需求。这里的主要问题是,没有办法,或者至少我找不到一个,有一个脚本可以直接关联CXPACKET等待类型与特定查询的增加。我发现的所有使用的解决方案都是从收集有关等待类型和并行化查询的信息开始的。从这里,他们分析这些信息,以找出可能的违规查询。

最后,我使用已知且经过测试的sp_whoIsActive stored procedure提出了以下解决方案,并且可以轻松恢复所有需要的信息。方法是在表上保存信息,等待类型数据和查询执行数据,然后查询此表以查找哪些查询可能是影响CXPACKET计数的查询。

在我的设置中,作业每运行1小时执行一个脚本,该脚本将在循环内调用sp_whoIsActive 10次,每次调用之间间隔5秒。只要需要恢复尽可能多的数据,您就可以让它保持运行。在可用于运行此sp的许多参数中,我使用@delta_interval = 2@destination_table = ‘table_name’@delta_interval触发了该过程的特殊用法。每次调用它将运行两次,间隔为2秒,然后将结果添加此2秒的差值。是一个既有历史总数又有delta值的好方法。通过在每次运行时使用@destination_table,结果将保存到名为Log_WhoIsActive的表中。稍后您可以通过我们可能感兴趣的任何列来查询此表过滤,在当前情况下,wait_info列包含有关等待类型的信息。在结果上,您还将拥有查询计划,查询本身以及可用于故障排除的更多数据。我的目标表名称为sh_wait_stats

这是一个示例查询,由CXPACKET在结果表上进行过滤:

SELECT *
FROM   dbo.Log_WhoIsActive
WHERE  wait_info LIKE '%CXPACKET%'
ORDER BY collection_time DESC

wait_info列的示例:

  

(4x:2/3 / 5ms)RUNNABLE,(3x:25983/25986 / 25988ms)CXPACKET:21,(2x:2 / 46ms)CXPACKET:13,(1x:54390ms)CXPACKET:2,(1x: 27ms)CXPACKET:9

其中:

  

(Ax:Bms / Cms / Dms)E          A是当前在资源类型E上等待的等待任务的数量.B / C / D是等待时间,以毫秒为单位。如果只有一个线程正在等待,则其等待时间将显示为B.如果两个任务正在等待,则将显示其每个等待时间(B / C)。如果三个或更多任务正在等待,将显示最小,平均和最大等待时间(B / C / D)。如果等待类型E是页面锁存等待并且页面是“特殊”类型(例如PFS,GAM,SGAM),则将识别页面类型。如果等待类型E是CXPACKET,则将识别查询计划中的nodeId

通过查看结果,可以使查询具有最多的CXPACKET等待类型并重复调用。这些查询很可能是导致CXPACKET等待类型上升的问题。

以下是我使用的脚本:

/**********************************************************************************
 * Step 1: Create a @destination_table
 *          
 * First, create your destination table. Sp_whoIsActive lets you generate a destination table easily.
 * Here is a basic version which includes the current date in the table name, as well as flags to get transaction 
 * info (tlog write info and duration) and query plans:
 ***********************************************************************************/
DECLARE @destination_table VARCHAR(4000) = 'dbo.Log_WhoIsActive'
        ,@schema VARCHAR(4000)
        ,@SQL NVARCHAR(500);

IF OBJECT_ID(@destination_table) IS NOT NULL 
BEGIN
    SELECT @SQL = 'DROP TABLE ' + @destination_table + '';
    EXEC sp_executesql @SQL;
END

EXEC [sp_WhoIsActive]
        @filter = '', 
        @filter_type = 'login',
        @not_filter = 'SQLSEARCHSERVER2',
        @not_filter_type = 'login',
        @get_task_info = 2,
        @get_plans = 2,
        @get_additional_info = 1,
        @show_sleeping_spids = 0,
        @delta_interval = 2,
        @output_column_list = '[dd%][session_id][query_plan][login_name][wait_info][tasks][status][host_name][database_name][program_name][sql_text][additional_info][tran_log%][cpu%][temp%][block%][reads%][writes%][context%][physical%][locks][%]',
        @RETURN_SCHEMA = 1, 
        @SCHEMA = @schema OUTPUT;

SET @schema = REPLACE(@schema, '<table_name>', @destination_table);

EXEC (@schema);

/***************************************************************************************
 * Step 2: Create a loop to periodically log data
 * Adding this step on a job makes it to run periodically to keep historical data
 * You need to need to make sure to specify the same parameters for sp_whoisactive in this loop, so the output matches the schema
 * for the table you’ve created.
 * This does 10 runs with a 5 second wait between runs. Depending on what’s going on, I change those numbers accordingly.
 ****************************************************************************************/
DECLARE @destination_table VARCHAR(4000) = 'dbo.Log_WhoIsActive'
        ,@msg NVARCHAR(1000)
        ,@numberOfRuns INT = 10;

WHILE @numberOfRuns > 0
BEGIN
    EXEC [sp_WhoIsActive]
            @filter = '', 
            @filter_type = 'login',
            @not_filter = 'SQLSEARCHSERVER2',
            @not_filter_type = 'login',
            @get_task_info = 2,
            @get_plans = 2,
            @get_additional_info = 1,
            @show_sleeping_spids = 0,
            @delta_interval = 5,
            @output_column_list = '[dd%][session_id][query_plan][login_name][wait_info][tasks][status][host_name][database_name][program_name][sql_text][additional_info][tran_log%][cpu%][temp%][block%][reads%][writes%][context%][physical%][locks][%]',
            @destination_table = @destination_table;

    SET @numberOfRuns = @numberOfRuns - 1 ;

    IF @numberOfRuns > 0
    BEGIN
         WAITFOR DELAY '00:00:05'
    END
END

/************************************************************************************
* Use this script to read data from historical table  
************************************************************************************/

DECLARE @max_increment_id INT 

--Determine most-recent increment_id 
SELECT @max_increment_id = MAX(increment_id) FROM dbo.sh_wait_stats 

--Present Waits results for period 
SELECT DOWS1.wait_type,
       (DOWS1.waiting_tasks_count - DOWS2.waiting_tasks_count) AS [diff_waiting_tasks_count],
       (DOWS1.wait_time_ms - DOWS2.wait_time_ms) AS [wait_time_ms],
       DOWS1.max_wait_time_ms,
       (DOWS1.signal_wait_time_ms - DOWS2.signal_wait_time_ms) AS  [diff_signal_wait_time_ms],
       DATEDIFF(ms, DOWS2.capture_time, DOWS1.capture_time) AS [diff_elapsed_time_ms],
       DOWS1.capture_time  AS [last_time_stamp],
       DOWS2.capture_time  AS [previous_time_stamp]
FROM   (
           SELECT wait_type,
                  waiting_tasks_count,
                  wait_time_ms,
                  max_wait_time_ms,
                  signal_wait_time_ms,
                  capture_time,
                  increment_id
           FROM   dbo.sh_wait_stats
           WHERE  increment_id = @max_increment_id
       )                   AS DOWS1
       INNER JOIN (
                SELECT wait_type,
                       waiting_tasks_count,
                       wait_time_ms,
                       max_wait_time_ms,
                       signal_wait_time_ms,
                       capture_time,
                       increment_id
                FROM   dbo.sh_wait_stats
                WHERE  increment_id = (@max_increment_id - 1)
            )              AS DOWS2
            ON  DOWS1.wait_type = DOWS2.wait_type
WHERE  (DOWS1.wait_time_ms - DOWS2.wait_time_ms) > 0
AND DOWS1.wait_type NOT IN (
        'CLR_SEMAPHORE', 'LAZYWRITER_SLEEP', 'RESOURCE_QUEUE', 'SLEEP_TASK',
        'SLEEP_SYSTEMTASK', 'SQLTRACE_BUFFER_FLUSH', 'WAITFOR', 'LOGMGR_QUEUE',
        'CHECKPOINT_QUEUE', 'REQUEST_FOR_DEADLOCK_SEARCH', 'XE_TIMER_EVENT', 'BROKER_TO_FLUSH',
        'BROKER_TASK_STOP', 'CLR_MANUAL_EVENT', 'CLR_AUTO_EVENT', 'DISPATCHER_QUEUE_SEMAPHORE',
        'FT_IFTS_SCHEDULER_IDLE_WAIT', 'XE_DISPATCHER_WAIT', 'XE_DISPATCHER_JOIN', 'BROKER_EVENTHANDLER',
        'TRACEWRITE', 'FT_IFTSHC_MUTEX', 'SQLTRACE_INCREMENTAL_FLUSH_SLEEP',
        'BROKER_RECEIVE_WAITFOR', 'ONDEMAND_TASK_QUEUE', 'DBMIRROR_EVENTS_QUEUE',
        'DBMIRRORING_CMD', 'BROKER_TRANSMITTER', 'SQLTRACE_WAIT_ENTRIES',
        'SLEEP_BPOOL_FLUSH', 'SQLTRACE_LOCK')
ORDER BY
       (DOWS1.wait_time_ms - DOWS2.wait_time_ms) DESC;

--Present Waits results for period 
WITH Waits AS 
(
    SELECT DOWS1.wait_type,
           ((DOWS1.wait_time_ms - DOWS2.wait_time_ms) / 1000) AS [wait_time_s],
           100. * (DOWS1.wait_time_ms - DOWS2.wait_time_ms) / SUM(DOWS1.wait_time_ms - DOWS2.wait_time_ms) 
           OVER()  AS pct,
           ROW_NUMBER() OVER(ORDER BY(DOWS1.wait_time_ms - DOWS2.wait_time_ms) DESC) AS 
           rn
    FROM   (
               SELECT wait_type,
                      waiting_tasks_count,
                      wait_time_ms,
                      max_wait_time_ms,
                      signal_wait_time_ms,
                      capture_time,
                      increment_id
               FROM   dbo.sh_wait_stats
               WHERE  increment_id = @max_increment_id
           )       AS DOWS1
           INNER JOIN (
                    SELECT wait_type,
                           waiting_tasks_count,
                           wait_time_ms,
                           max_wait_time_ms,
                           signal_wait_time_ms,
                           capture_time,
                           increment_id
                    FROM   dbo.sh_wait_stats
                    WHERE  increment_id = (@max_increment_id - 1)
                )  AS DOWS2
                ON  DOWS1.wait_type = DOWS2.wait_type
    WHERE  (DOWS1.wait_time_ms - DOWS2.wait_time_ms) > 0
) 
SELECT W1.wait_type,
       CAST(W1.wait_time_s AS DECIMAL(12, 2)) AS wait_time_s,
       CAST(W1.pct AS DECIMAL(12, 2))  AS pct,
       CAST(SUM(W2.pct) AS DECIMAL(12, 2)) AS running_pct
FROM   Waits                           AS W1
       INNER JOIN Waits                AS W2
            ON  W2.rn <= W1.rn
GROUP BY
       W1.rn,
       W1.wait_type,
       W1.wait_time_s,
       W1.pct
HAVING SUM(W2.pct) - W1.pct < 95; -- percentage threshold;