waitresource =“ PAGE:6:3:70133”
waitresource =“ KEY:6:72057594041991168(ce52f92a058c)”
有时,您正在学习的巨型XML中会包含更多信息(死锁的图包含有助于您找到对象和索引名称的资源列表),但并非总是如此。
本文将帮助您解密它们。
这里的所有信息都在Internet的各个位置上,它们只是高度分散的!我想将所有内容放在一起-从DBCC PAGE到hobt_id,以及未记录的%% physloc %%和%% lockres %%函数。
首先,让我们谈谈等待PAGE锁,然后再转到KEY锁。
1)waitresource =“ PAGE:6:3:70133” = Database_Id:FileId:PageNumber
如果查询正在等待PAGE锁定,则SQL Server将为您提供该页面的URL。
分解“ PAGE:6:3:70133”,我们得到:
- database_id = 6
- data_file_id = 3
- page_numer = 70133
1.1)解密database_id
让我们使用查询找到数据库的名称:
SELECT
name
FROM sys.databases
WHERE database_id=6;
GO
这是我的SQL Server上的公共WideWorldImporters数据库。
1.2)寻找数据文件的名称-如果您有兴趣
下一步,我们将使用data_file_id查找表名。您可以直接进行下一步,但是如果您对文件名感兴趣,可以通过在找到的数据库的上下文中运行查询来代替它,方法是在该查询中替换data_file_id:
USE WideWorldImporters;
GO
SELECT
name,
physical_name
FROM sys.database_files
WHERE file_id = 3;
GO
在WideWorldImporters数据库中,这是一个名为WWI_UserData的文件,我将其还原到C:\ MSSQL \ DATA \ WideWorldImporters_UserData.ndf。(糟糕,您让我把文件放到系统磁盘上!不!太尴尬了)。
1.3)从DBCC PAGE获取对象的名称
现在我们知道数据文件3中的#70133页属于WorldWideImporters数据库。我们可以使用未记录的DBCC PAGE和跟踪标志3604查看此页面的内容。
注意:我更喜欢在从其他服务器上某处的备份还原的备份上使用DBCC PAGE,因为这是未记录的。在某些情况下,它可能导致创建转储(不幸的是,翻译者的评论-该链接无处可寻,但是从URL的角度来看,我们正在谈论过滤索引)。
/* This trace flag makes DBCC PAGE output go to our Messages tab
instead of the SQL Server Error Log file */
DBCC TRACEON (3604);
GO
/* DBCC PAGE (DatabaseName, FileNumber, PageNumber, DumpStyle)*/
DBCC PAGE ('WideWorldImporters',3,70133,2);
GO
向下滚动到结果,可以找到object_id和index_id。
马上就好了!现在,您可以使用查询找到表和索引名称:
USE WideWorldImporters;
GO
SELECT
sc.name as schema_name,
so.name as object_name,
si.name as index_name
FROM sys.objects as so
JOIN sys.indexes as si on
so.object_id=si.object_id
JOIN sys.schemas AS sc on
so.schema_id=sc.schema_id
WHERE
so.object_id = 94623380
and si.index_id = 1;
GO
现在,我们看到对锁的等待在Sales.OrderLines表的PK_Sales_OrderLines索引上。
注意:在SQL Server 2014及更高版本中,还可以使用未记录的DMO sys.dm_db_database_page_allocations找到对象名称。但是您必须查询数据库中的每个页面,这对于大型数据库而言似乎并不很酷,所以我使用了DBCC PAGE。
1.4)您可以在被阻止的页面上看到数据吗?
Nuuu是的但是...您确定您确实需要吗?
即使在小桌子上也很慢。但这很酷,因此,既然您已经阅读了这么多……让我们来谈谈%% physloc %%!
%% physloc %%是未记录的魔法,它为每个条目返回一个物理ID。您可以在SQL Server 2008及更高版本中将%% physloc %%与sys.fn_PhysLocFormatter一起使用。
现在我们知道我们要阻止Sales.OrderLines中的页面,我们可以使用以下查询查看该表中的所有数据,该数据存储在第70133页的数据文件#3中:
Use WideWorldImporters;
GO
SELECT
sys.fn_PhysLocFormatter (%%physloc%%),
*
FROM Sales.OrderLines (NOLOCK)
WHERE sys.fn_PhysLocFormatter (%%physloc%%) like '(3:70133%'
GO
正如我所说-即使在很小的桌子上,速度也很慢。我向查询添加了NOLOCK,因为我们仍然无法保证我们要查看的数据与找到锁时的数据完全相同-因此我们可以安全地进行脏读。
但是,欢呼,此查询返回的查询行数与我查询的25行相同,
足够用于PAGE锁定。如果我们正在等待KEY锁怎么办?
2)waitresource =“ KEY:6:72057594041991168(ce52f92a058c)” = Database_Id,HOBT_Id(可以使用%% lockres %%解密的魔术哈希,如果您确实愿意的话)
如果您的查询试图锁定索引条目并锁定自身,那么您将获得完全不同的地址类型。
将“ 6:72057594041991168(ce52f92a058c)”分解成几部分,我们得到:
- database_id = 6
- hobt_id = 72057594041991168
- 魔术哈希=(ce52f92a058c)
2.1)解密database_id
这与上面的示例完全相同!使用查询找到数据库的名称:
SELECT
name
FROM sys.databases
WHERE database_id=6;
GO
就我而言,这是相同的WideWorldImporters数据库。
2.2)解密hobt_id
在找到的数据库的上下文中,您需要使用几个联接对sys.partitions进行查询,这将帮助您确定表和索引名...
USE WideWorldImporters;
GO
SELECT
sc.name as schema_name,
so.name as object_name,
si.name as index_name
FROM sys.partitions AS p
JOIN sys.objects as so on
p.object_id=so.object_id
JOIN sys.indexes as si on
p.index_id=si.index_id and
p.object_id=si.object_id
JOIN sys.schemas AS sc on
so.schema_id=sc.schema_id
WHERE hobt_id = 72057594041991168;
GO
它告诉我请求正在使用PK_Application_Countries索引在Application.Countries锁上等待。
2.3)现在,一些不可思议的%% lockres %%-如果您想找出被锁定的记录
如果我真的想知道锁需要在哪一行,可以通过查询表本身来弄清楚。我们可以使用未记录的%% lockres %%函数来查找与魔术哈希匹配的条目。
请注意,此查询将扫描整个表,而在大型表上,这可能根本没有意思:
SELECT
*
FROM Application.Countries (NOLOCK)
WHERE %%lockres%% = '(ce52f92a058c)';
GO
我添加了NOLOCK(在Twitter上Klaus Aschenbrenner的建议下),因为阻塞可能是一个问题。我们只想看看现在有什么,而不是开始事务开始时的什么-我认为数据一致性对我们来说并不重要。
瞧,我们争取的纪录!
致谢和进一步阅读
我不记得是谁第一个描述了许多这样的事情,但是这里有两篇关于您可能不愿记载的东西的文章:
- 保罗·兰德尔(Paul Randal)发表了有关%% physloc %%和sys.fn_PhysLocFormatter的帖子(正如我们在第一个示例中所做的那样)
- 关于StackOverflow的一个有关使用%% lockres %%的问题(我们在第二个示例中找到了数据)。其中一封回覆是Grant Fritchey在2010年写回的有关%% lockres %%的文章。