18

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

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;

这是“让我成为数字表”的查询之一。

这就是问题所在。如果我在 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;

然后它会像您期望的那样工作——第一个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;

这也使得第二次SELECT运行永远。 单表版本也是如此。不知何故,那个三桌版本很神奇!

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

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


添加信息:

  • 这是 SQL Server 2012 开发者版
  • EXEC sp_WhoIsActive @find_block_leaders = 1, @sort_order = '[blocked_session_count] DESC'(脚本为 XML,因此可以在此处阅读)的输出是:
<?xml 版本="1.0" ?>
<结果1>
    <记录>
        <dd hh:mm:ss.mss>00 00:10:45.066</dd hh:mm:ss.mss>
        <session_id>52</session_id>
        <sql_text><?查询 --
SELECT TOP 1000000 IDENTITY(INT, 1, 1) 数字
INTO数字
FROM sys.objects s1
交叉连接 sys.objects s2
交叉连接 sys.objects s3
交叉连接 sys.objects s4;

--?></sql_text>
        <login_name>我自己的登录名已编辑</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>
        <读取> 583,273</读取>
        <写> 537</写>
        <physical_reads> 50</physical_reads>
        <used_memory> 3</used_memory>
        <status>暂停</status>
        <open_tran_count> 2</open_tran_count>
        <percent_complete>NULL</percent_complete>
        <host_name>我自己的机器名称已编辑</host_name>
        <database_name>tempdb</database_name>
        <program_name>Microsoft SQL Server Management Studio - 查询</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>
    </记录>
</结果1>

更多补充信息:

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

4

2 回答 2

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 

重现步骤

首先创建一个过程

CREATE PROC P
AS
    SET NOCOUNT ON;

    DECLARE @T TABLE (X INT)
GO

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

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 

然后在另一个连接中运行

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

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

我设法以这种方式阻止了会话 id 53。它被无限期地封锁。的输出sp_WhoIsActive显示此 spid 几乎所有时间都处于暂停状态。在连续运行中,reads列中的数字会增加,但其他列中的值基本保持不变。

等待持续时间没有显示出增加的模式,尽管表明它必须在再次被阻塞之前定期解除阻塞。

SELECT *
FROM   sys.dm_os_waiting_tasks
WHERE  session_id = blocking_session_id

退货

+----------------------+------------+-----------------+------------------+-----------+--------------------+-----------------------+---------------------+--------------------------+--------------------------------------------------------------------------------------------------+
| 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 |
+----------------------+------------+-----------------+------------------+-----------+--------------------+-----------------------+---------------------+--------------------------+--------------------------------------------------------------------------------------------------+

在资源描述中使用 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 

退货

+------------+
|    name    |
+------------+
| sysschobjs |
+------------+

跑步

SELECT resource_description,request_status
FROM   sys.dm_tran_locks 
WHERE request_session_id = 53 AND request_status <> 'GRANT'

退货

+----------------------+----------------+
| resource_description | request_status |
+----------------------+----------------+
| (246708db8c1f)       | CONVERT        |
+----------------------+----------------+

通过 DAC 连接并运行

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

退货

+-------------+-----------+
|     id      |   name    |
+-------------+-----------+
| -1578606288 | #A1E86130 |
+-------------+-----------+

好奇那是什么

SELECT name,user_type_id
FROM tempdb.sys.columns
WHERE object_id = -1578606288 

退货

+------+--------------+
| name | user_type_id |
+------+--------------+
| X    |           56 |
+------+--------------+

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

跑步

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)' 

退货

+--------------+----------------+--------------------+------------------+--------------------+----------------+-------------+-------------------------+
| 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 |
+--------------+----------------+--------------------+------------------+--------------------+----------------+-------------+-------------------------+

所以事务在与表变量有关的行上SELECT INTO持有一个锁。由于此锁冲突,事务无法获得该行的锁。Stempdb.sys.sysschobjs#A1E86130droptempXS

重复运行此查询会发现transaction_idfordroptemp事务重复更改。

我推测 SQL Server 必须在用户 spid 上分配这些内部事务,并在执行用户工作之前对它们进行优先级排序。因此,会话 id 53 卡在启动droptemp事务的恒定循环中,被在同一 spid 上运行的用户事务阻止。回滚内部事务,然后无限期地重复该过程。

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

探查器

我还跟踪了之前的锁定事件。

锁定事件阻塞

LockAquisitionPatternBlockingTransaction

SELECT INTO大部分由事务取出的共享密钥锁sysschobjs都会立即释放。例外是第一次锁定(246708db8c1f)

这是有道理的,因为该计划显示了嵌套循环扫描,[sys].[sysschobjs].[clst] [o]并且因为临时对象被赋予负的 objectid,它们将是扫描顺序中遇到的第一行。

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

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

LockAquisitionPatternNonBlockingTransaction

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

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

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


更新

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

于 2013-11-24T15:37:43.290 回答
9

与其追这个问题,不如你在model数据库中创建一次表,然后它会tempdb自动为你创建?

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

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

  1. 对于试图创建表的 spid,sys.dm_exec_requests说什么wait_type
  2. 它有一个blocking_session_id吗?
  3. 如果是这样,会话在做什么?
于 2013-11-22T22:19:15.033 回答