为什么在我启动服务后第一次查询速度变慢?

Ata*_*rio 17 sql-server tempdb

好.这是我尝试运行的内容:

USE tempdb;

SELECT TOP 1000000 IDENTITY(INT, 1, 1) Number
INTO Numbers
FROM sys.objects s1
CROSS JOIN sys.objects s2
CROSS JOIN sys.objects s3
CROSS JOIN sys.objects s4;
Run Code Online (Sandbox Code Playgroud)

这是"让我成为数字表"查询之一.

这是问题所在.如果我在(重新)启动SQL Server服务后立即运行它,则需要永久.不是十分之一,我希望它更快.永远如此,我让它一次又一次地超过两个小时,仍然不得不杀死它.我在想它永远不会回来.通常我的机器上运行它需要两秒钟.

但是,如果我这样做:

USE tempdb;

SELECT TOP 1000000 IDENTITY(INT, 1, 1) Number
INTO Numbers
FROM sys.objects s1
CROSS JOIN sys.objects s2
CROSS JOIN sys.objects s3;

DROP TABLE Numbers;

SELECT TOP 1000000 IDENTITY(INT, 1, 1) Number
INTO Numbers
FROM sys.objects s1
CROSS JOIN sys.objects s2
CROSS JOIN sys.objects s3
CROSS JOIN sys.objects s4;
Run Code Online (Sandbox Code Playgroud)

然后它就像你期望的那样工作 - 第一次SELECT运行在两秒钟内,第二次运行.为什么我不使用三表版本?因为没有足够的条目sys.objects用于该数字的立方数等于一百万个结果行.但这甚至不再是重点.

无论如何,从这里开始,我可以重复第二次DROP/ SELECT…INTO尽可能多,没问题.不知何故,第一个三桌版本让它永远存在.至少,直到下次重新启动服务和/或重新启动机器.在这一点上,SELECT再次运行那个永远不会回来.再次.

这是它开始变得更加怪异的地方.如果我先把它SELECT缩回到两个表的版本:

USE tempdb;

SELECT TOP 1000000 IDENTITY(INT, 1, 1) Number
INTO Numbers
FROM sys.objects s1
CROSS JOIN sys.objects s2;

DROP TABLE Numbers;

SELECT TOP 1000000 IDENTITY(INT, 1, 1) Number
INTO Numbers
FROM sys.objects s1
CROSS JOIN sys.objects s2
CROSS JOIN sys.objects s3
CROSS JOIN sys.objects s4;
Run Code Online (Sandbox Code Playgroud)

这也使得第二次SELECT运行永远. 和单表版本一样.不知何故,三表版本是神奇的!

这里发生了什么?为什么这么慢?

(并且在任何人指出我正在创建一个永久表之前tempdb,是的,我知道.改变到实际临时表没有任何区别.)


补充信息:

  • 这是SQL Server 2012 Developer Edition
  • 输出EXEC sp_WhoIsActive @find_block_leaders = 1, @sort_order = '[blocked_session_count] DESC'(以XML格式编写,因此可以在此处阅读)是:
<?xml version="1.0" ?>
<RESULTS1>
    <RECORD>
        <dd hh:mm:ss.mss>00 00:10:45.066</dd hh:mm:ss.mss>
        <session_id>52</session_id>
        <sql_text>&lt;?query --
SELECT TOP 1000000 IDENTITY(INT, 1, 1) Number
INTO Numbers
FROM sys.objects s1
CROSS JOIN sys.objects s2
CROSS JOIN sys.objects s3
CROSS JOIN sys.objects s4;

--?&gt;</sql_text>
        <login_name>my own login name redacted</login_name>
        <wait_info>(99ms)LCK_M_X</wait_info>
        <CPU>              9,750</CPU>
        <tempdb_allocations>                713</tempdb_allocations>
        <tempdb_current>                702</tempdb_current>
        <blocking_session_id>NULL</blocking_session_id>
        <blocked_session_count>                  0</blocked_session_count>
        <reads>            583,273</reads>
        <writes>                537</writes>
        <physical_reads>                 50</physical_reads>
        <used_memory>                  3</used_memory>
        <status>suspended</status>
        <open_tran_count>                  2</open_tran_count>
        <percent_complete>NULL</percent_complete>
        <host_name>my own machine name redacted</host_name>
        <database_name>tempdb</database_name>
        <program_name>Microsoft SQL Server Management Studio - Query</program_name>
        <start_time>2013-11-23 23:48:19.473</start_time>
        <login_time>2013-11-23 23:47:47.060</login_time>
        <request_id>0</request_id>
        <collection_time>2013-11-23 23:59:04.560</collection_time>
    </RECORD>
</RESULTS1>

更多信息:

为什么我把它放在tempdb中是因为它是一个旨在在原始安装上运行的脚本的一部分,并且tempdb保证在那里.正如我所说,更改为全局临时表并没有什么不同.

Mar*_*ith 24

可以在我的机器上100%重现这个.(见末尾注)

问题的关键在于您正在取消S对系统表行tempdb的锁定,这可能与内部tempdb清理事务所需的锁定冲突.

当此清理工作分配给拥有S锁的同一会话时,可能会发生无限期挂起.

为了避免这个问题,你需要停止引用system里面的对象tempdb.

可以在不引用任何外部表的情况下创建数字表.以下需要不读取基表行,因此也不需要锁定.

WITH Ten(N) AS 
(
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL 
    SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
)   
SELECT TOP 1000000 IDENTITY(INT, 1, 1) Number
INTO   Numbers
FROM   Ten T10,
       Ten T100,
       Ten T1000,
       Ten T10000,
       Ten T100000,
       Ten T1000000 
Run Code Online (Sandbox Code Playgroud)

重现步骤

首先创建一个过程

CREATE PROC P
AS
    SET NOCOUNT ON;

    DECLARE @T TABLE (X INT)
GO
Run Code Online (Sandbox Code Playgroud)

然后重新启动SQL Service并在一个连接中执行

WHILE NOT EXISTS(SELECT *
                 FROM   sys.dm_os_waiting_tasks
                 WHERE  session_id = blocking_session_id)
  BEGIN

      /*This will cause the problematic droptemp transactions*/
      EXEC sp_recompile 'P'

      EXEC P
  END;

SELECT *
FROM   sys.dm_os_waiting_tasks
WHERE  session_id = blocking_session_id 
Run Code Online (Sandbox Code Playgroud)

然后在另一个连接运行

USE tempdb;

SELECT TOP 1000000 IDENTITY(INT, 1, 1) Number
INTO #T
FROM sys.objects s1
CROSS JOIN sys.objects s2
CROSS JOIN sys.objects s3
CROSS JOIN sys.objects s4;

DROP TABLE #T
Run Code Online (Sandbox Code Playgroud)

填充Numbers表的查询似乎设法进入实时锁定情况,内部系统事务清理临时对象(如表变量).

我设法以这种方式阻止会话ID 53.它被无限期阻止.输出sp_WhoIsActive表明该SPID花费几乎所有的时间暂停.在连续运行中,reads列中的数字会增加,但其他列中的值保持大致相同.

等待持续时间并未显示增加的模式,但表示必须在再次被阻止之前定期取消阻止.

SELECT *
FROM   sys.dm_os_waiting_tasks
WHERE  session_id = blocking_session_id
Run Code Online (Sandbox Code Playgroud)

返回

+----------------------+------------+-----------------+------------------+-----------+--------------------+-----------------------+---------------------+--------------------------+--------------------------------------------------------------------------------------------------+
| waiting_task_address | session_id | exec_context_id | wait_duration_ms | wait_type |  resource_address  | blocking_task_address | blocking_session_id | blocking_exec_context_id |                                       resource_description                                       |
+----------------------+------------+-----------------+------------------+-----------+--------------------+-----------------------+---------------------+--------------------------+--------------------------------------------------------------------------------------------------+
| 0x00000002F2C170C8   |         53 |               0 |               86 | LCK_M_X   | 0x00000002F9B13040 | 0x00000002F2C170C8    |                  53 | NULL                     | keylock hobtid=281474978938880 dbid=2 id=lock2f9ac8880 mode=U associatedObjectId=281474978938880 |
+----------------------+------------+-----------------+------------------+-----------+--------------------+-----------------------+---------------------+--------------------------+--------------------------------------------------------------------------------------------------+
Run Code Online (Sandbox Code Playgroud)

在资源描述中使用id

SELECT o.name
FROM   sys.allocation_units au WITH (NOLOCK)
       INNER JOIN sys.partitions p WITH (NOLOCK)
         ON au.container_id = p.partition_id
       INNER JOIN sys.all_objects o WITH (NOLOCK)
         ON o.object_id = p.object_id
WHERE  allocation_unit_id = 281474978938880 
Run Code Online (Sandbox Code Playgroud)

返回

+------------+
|    name    |
+------------+
| sysschobjs |
+------------+
Run Code Online (Sandbox Code Playgroud)

运行

SELECT resource_description,request_status
FROM   sys.dm_tran_locks 
WHERE request_session_id = 53 AND request_status <> 'GRANT'
Run Code Online (Sandbox Code Playgroud)

返回

+----------------------+----------------+
| resource_description | request_status |
+----------------------+----------------+
| (246708db8c1f)       | CONVERT        |
+----------------------+----------------+
Run Code Online (Sandbox Code Playgroud)

通过DAC连接并运行

SELECT id,name
FROM   tempdb.sys.sysschobjs WITH (NOLOCK)
WHERE %%LOCKRES%% = '(246708db8c1f)' 
Run Code Online (Sandbox Code Playgroud)

返回

+-------------+-----------+
|     id      |   name    |
+-------------+-----------+
| -1578606288 | #A1E86130 |
+-------------+-----------+
Run Code Online (Sandbox Code Playgroud)

对这是什么感到好奇

SELECT name,user_type_id
FROM tempdb.sys.columns
WHERE object_id = -1578606288 
Run Code Online (Sandbox Code Playgroud)

返回

+------+--------------+
| name | user_type_id |
+------+--------------+
| X    |           56 |
+------+--------------+
Run Code Online (Sandbox Code Playgroud)

这是存储过程使用的表变量中的列名.

运行

SELECT request_mode,
       request_status,
       request_session_id,
       request_owner_id,
       lock_owner_address,
       t.transaction_id,
       t.name,
       t.transaction_begin_time
FROM   sys.dm_tran_locks l
       JOIN sys.dm_tran_active_transactions t
         ON l.request_owner_id = t.transaction_id
WHERE  resource_description = '(246708db8c1f)' 
Run Code Online (Sandbox Code Playgroud)

返回

+--------------+----------------+--------------------+------------------+--------------------+----------------+-------------+-------------------------+
| request_mode | request_status | request_session_id | request_owner_id | lock_owner_address | transaction_id |    name     | transaction_begin_time  |
+--------------+----------------+--------------------+------------------+--------------------+----------------+-------------+-------------------------+
| U            | GRANT          |                 53 |           227647 | 0x00000002F1EF6800 |         227647 | droptemp    | 2013-11-24 18:36:28.267 |
| S            | GRANT          |                 53 |           191790 | 0x00000002F9B16380 |         191790 | SELECT INTO | 2013-11-24 18:21:30.083 |
| X            | CONVERT        |                 53 |           227647 | 0x00000002F9B12FC0 |         227647 | droptemp    | 2013-11-24 18:36:28.267 |
+--------------+----------------+--------------------+------------------+--------------------+----------------+-------------+-------------------------+
Run Code Online (Sandbox Code Playgroud)

因此,SELECT INTO事务持有与表变量相关S的行的锁定.由于此冲突的锁定,事务无法锁定此行.tempdb.sys.sysschobjs#A1E86130droptempXS

重复运行此查询会显示transaction_iddroptemp事务的重复更改.

我推测SQL Server必须在用户spid上分配这些内部事务,并在执行用户工作之前确定它们的优先级.因此,会话ID 53停留在启动droptemp事务的恒定周期中,被在同一spid上运行的用户事务阻塞.回滚内部事务,然后无限期地重复该过程.

通过在spid挂起后跟踪SQL Server Profiler中的各种锁定和事务事件来证实这一点.

探查

在此之前我还跟踪了锁定事件.

锁定事件阻止

LockAquisitionPatternBlockingTransaction

SELECT INTO关键字中的事务取出的大多数共享密钥锁sysschobjs立即释放.第一次锁定就是例外(246708db8c1f).

这有点意义,因为计划显示嵌套循环扫描,[sys].[sysschobjs].[clst] [o]并且因为临时对象被给予否定对象,它们将是扫描顺序中遇到的第一行.

我还遇到了OP中描述的情况,其中首先运行三向交叉连接似乎允许四路交叉连接成功.

SELECT INTO事务跟踪中的前几个事件有一个完全不同的模式.

LockAquisitionPatternNonBlockingTransaction

这是在服务重新启动之后,因此文本数据列中的锁定资源值无法直接比较.

而不是保留第一个键上的锁,然后获取和释放后续键的模式,它似乎获得了更多的锁,而不是最初释放它们.

我认为执行策略必须有一些差异才能避免这个问题.


更新

我提出的关于这个的连接项目没有被标记为已修复,但我现在在SQL Server 2012 SP2上,现在只能重现临时自我阻止而不是永久阻止.我仍然得到自我阻止但是在droptemp成功执行事务的一些失败尝试之后,它似乎又回到处理用户事务.之后提交系统事务然后成功执行.仍在同一个spid上.(在一个示例中运行8次尝试.我不确定这是否会一直重复)

  • 有几点仍未解决.(1)为什么在OP中描述了4个交叉连接而不是3个交叉连接.(2)如何将这些内部事务分配给特定的spid.在此期间[我在这里发布了一个连接项目报告](https://connect.microsoft.com/SQLServer/feedback/details/809544/internal-droptemp-transaction-can-cause-unkillable-spid) (3认同)

Aar*_*and 9

而不是追逐这个问题,为什么不在model数据库中创建一次表,那么它将tempdb自动为您创建?

对于实际问题,我们不知道.我的第一个猜测是你的tempdb文件的初始大小非常小(比如,1MB).因此,在创建表时,必须扩展文件以适应它.这可能非常昂贵,特别是如果您没有启用即时文件初始化,并且增长日志以适应其中所需的活动也可能非常昂贵.

除此之外,我们可以继续猜测,但会更适合调查实际发生的事情.你想问的问题:

  1. 对于试图创建表的spid,有什么sys.dm_exec_requestswait_type呢?
  2. 它有blocking_session_id吗?
  3. 如果是这样,会话在做什么?