How to I force lock escalation (to flush out deadlock issues) during testing?

只愿长相守 提交于 2019-12-12 14:21:24

问题


Posting the question and one answer here. Maybe somebody has better answer...

It's possible to write code that triggers a deadlock even for a single user if the developer accidentally opens up a second connection to the database instead of reusing the an existing one (which might already have an open transaction). Certain O/RM and LINQ frameworks make this mistake easy.

Here is the scenario in pure SQL terms:

--Setup:
CREATE TABLE Vendors(VendorID int NOT NULL, VendorName nvarchar(100) NOT NULL, ExtraColumn int not null, CONSTRAINT PK_Vendors PRIMARY KEY CLUSTERED(VendorID))
GO
INSERT INTO Vendors(VendorID,VendorName,ExtraColumn) VALUES (1, 'Microsoft', 12345)
INSERT INTO Vendors(VendorID,VendorName,ExtraColumn) VALUES (2, 'Oracle', 12345)

--Connection 1:
BEGIN TRANSACTION
UPDATE Vendors SET ExtraColumn = 222 WHERE VendorID = 2

--Connection 2:
BEGIN TRANSACTION
SELECT VendorName FROM Vendors WHERE VendorID = 1

This code (and the c#/java/ORM/LINQ/whatever that generated it) might run happily in development/testing when the data is small, but suddenly deadlock when the data/memory profile changes in production and the lock escalates from row to page to table.

So how can I force lock escalation up to table level in my test environment so I can flush out any bugs like this (where the code opens up the 2nd connection)?

Lock escalation is completely controlled by the SQL Server database engine and it's not possible to predict when it will escalate from row lock to table lock. I won't rehash all the details which you can find here, but one in particular to note: "Lock escalation is triggered when...The number of locks in an instance of the Database Engine exceeds memory or configuration thresholds." This means that it could be completely unrelated to my carefully-crafted code and perfectly-selected indices.


回答1:


One approach is to:

  1. In the test environment, run a script like the following to disable row and page locking on all indexes (making it go directly to table lock)
  2. Run the tests
  3. Run the script again setting it back to normal locking
--
-- Script to disable/enable row & page locking on dev/test environment to flush out deadlock issues
-- executes statement like this for each table in the database:
-- ALTER INDEX indexname ON tablename SET ( ALLOW_ROW_LOCKS  = OFF, ALLOW_PAGE_LOCKS  = OFF )
--
-- DO NOT RUN ON A PRODUCTION DATABASE!!!!
--
set nocount on
declare @newoption varchar(3)
--------------------------------------------------------------------
-- Change variable below to 'ON' or 'OFF' --------------------------
--   'OFF' means row & page locking is disabled and everything 
--      triggers a table lock
--   'ON' means row & page locking is enabled and the server chooses
--     how to escalate the locks (this is the default setting)
set @newoption = 'OFF'
--------------------------------------------------------------------

DECLARE    @TableName varchar(300)
DECLARE    @IndexName varchar(300)
DECLARE @sql varchar(max)

DECLARE inds CURSOR FAST_FORWARD FOR
SELECT tablename, indname
FROM (
    select top 100 percent
    so.name as tablename
         , si.indid
         , si.name as indname
         , INDEXPROPERTY( si.id, si.name, 'IsPageLockDisallowed') as IsPageLockDisallowed
         , INDEXPROPERTY( si.id, si.name, 'IsRowLockDisallowed') as IsRowLockDisallowed
    from   sysindexes si
    join sysobjects so on si.id = so.id
    where  si.status & 64 = 0
      and  objectproperty(so.id, 'IsMSShipped') = 0
      and si.name is not null
      and so.name not like 'aspnet%'
      and so.name not like 'auditLog%'
    order by so.name, si.indid
) t

OPEN inds
FETCH NEXT FROM inds INTO @TableName, @IndexName

WHILE @@FETCH_STATUS = 0
BEGIN

    SET @sql = 'ALTER INDEX [' + @IndexName + '] ON [dbo].[' + @TableName + '] SET ( ALLOW_ROW_LOCKS  = ' + @newoption + ', ALLOW_PAGE_LOCKS  = ' + @newoption +' )'
    PRINT @sql
    EXEC(@sql)

    FETCH NEXT FROM inds INTO @TableName, @IndexName
END

CLOSE inds
DEALLOCATE inds


PRINT 'Done'

Other notes:

  • I got the core of the above script from somebody else's article, but I've long-since forgotten where. Apologies for the lack of attribution.
  • Note that the above script will overwrite any existing custom lock escalation settings on your tables. You can check for these by first running the inner select ("SELECT TOP 100 PERCENT so.name...") to see the existing settings.


来源:https://stackoverflow.com/questions/14779358/how-to-i-force-lock-escalation-to-flush-out-deadlock-issues-during-testing

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!