3

ここに質問と 1 つの回答を投稿します。誰かがより良い答えを持っているかもしれません...

開発者がデータベースへの 2 番目の接続を誤って開いた場合に、既存の接続 (既に開いているトランザクションがある可能性があります) を再利用するのではなく、1 人のユーザーに対してもデッドロックをトリガーするコードを作成することができます。特定の O/RM および LINQ フレームワークでは、この間違いが起こりやすくなっています。

純粋な SQL 用語でのシナリオは次のとおりです。

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

このコード (およびそれを生成した c#/java/ORM/LINQ) は、データが小さい場合は開発/テストで問題なく実行される可能性がありますが、運用環境でデータ/メモリ プロファイルが変更され、ロックが行から行にエスカレートすると、突然デッドロックになります。ページからテーブルへ。

では、このようなバグ(コードが 2 番目の接続を開く場所) をフラッシュできるように、テスト環境でテーブル レベルまで強制的にロックをエスカレーションするにはどうすればよいでしょうか?

ロックのエスカレーションは SQL Server データベース エンジンによって完全に制御されており、行ロックからテーブル ロックにエスカレートする時期を予測することはできません。ここで見つけることができるすべての詳細を再ハッシュすることはしませんが、特に注意すべき点は次のとおりです。これは、慎重に作成されたコードや完全に選択されたインデックスとはまったく無関係である可能性があることを意味します。

4

1 に答える 1

1

1 つの方法は次のとおりです。

  1. テスト環境で、次のようなスクリプトを実行して、すべてのインデックスで行とページのロックを無効にします (テーブル ロックに直接移動します)。
  2. テストを実行する
  3. スクリプトを再度実行して、通常のロックに戻します
--
-- 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'

その他の注意事項:

  • 上記のスクリプトの核心部分は他の人の記事から入手しましたが、長い間忘れていました。帰属の欠如についてお詫び申し上げます。
  • 上記のスクリプトは、テーブルの既存のカスタム ロック エスカレーション設定を上書きすることに注意してください。これらを確認するには、最初に内部選択 ("SELECT TOP 100 PERCENT so.name...") を実行して既存の設定を確認します。
于 2013-02-08T18:51:37.623 に答える