82

Stack Overflow SQL Server 2005データベースで、いくつかの有害ではあるがまれなデッドロック状態が発生しています。

プロファイラーを接続し、デッドロックのトラブルシューティングに関するこの優れた記事を使用してトレースプロファイルを設定し、多数の例をキャプチャしました。奇妙なことに、デッドロック書き込みは常に同じです:

UPDATE [dbo].[Posts]
SET [AnswerCount] = @p1, [LastActivityDate] = @p2, [LastActivityUserId] = @p3
WHERE [Id] = @p0

他のデッドロックステートメントはさまざまですが、通常は、投稿テーブルの簡単で単純な読み取りです。これは常にデッドロックで殺されます。これが例です

SELECT
[t0].[Id], [t0].[PostTypeId], [t0].[Score], [t0].[Views], [t0].[AnswerCount], 
[t0].[AcceptedAnswerId], [t0].[IsLocked], [t0].[IsLockedEdit], [t0].[ParentId], 
[t0].[CurrentRevisionId], [t0].[FirstRevisionId], [t0].[LockedReason],
[t0].[LastActivityDate], [t0].[LastActivityUserId]
FROM [dbo].[Posts] AS [t0]
WHERE [t0].[ParentId] = @p0

完全に明確にするために、書き込み/書き込みのデッドロックは発生していませんが、読み取り/書き込みが発生しています。

現在、LINQクエリとパラメータ化されたSQLクエリが混在しています。with (nolock)すべてのSQLクエリに追加しました。これはいくつかの助けになったかもしれません。また、昨日修正した1つの(非常に)不十分なバッジクエリがありました。これは、毎回実行するのに20秒以上かかり、その上で毎分実行されていました。これがいくつかのロックの問題の原因であることを望んでいました!

残念ながら、約2時間前に別のデッドロックエラーが発生しました。同じ正確な症状、同じ正確な犯人の書き込み。

本当に奇妙なことは、上記のロッキング書き込みSQLステートメントが非常に特定のコードパスの一部であるということです。新しい回答が質問に追加された場合にのみ実行されます。親の質問が新しい回答数と最終日/ユーザーで更新されます。これは、明らかに、私たちが行っている膨大な数の読み取りに比べてそれほど一般的ではありません!私の知る限り、アプリ内のどこにも大量の書き込みは行っていません。

NOLOCKは一種の巨大なハンマーであることに気づきましたが、ここで実行するクエリのほとんどはそれほど正確である必要はありません。ユーザープロファイルが数秒古くなっていてもかまいませんか?

スコット・ハンゼルマンがここで説明しているように、LinqでNOLOCKを使用するのは少し難しいです。

私たちは使用するという考えでいちゃつく

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

すべてのLINQクエリにこのセットが含まれるように、ベースデータベースコンテキストで。それがなければ、私たちが行うすべてのLINQ呼び出し(まあ、それらの大部分である単純な読み取り呼び出し)を3〜4行のトランザクションコードブロックでラップする必要があります。これは醜いです。

SQL 2005での些細な読み取りによって、書き込みがデッドロックする可能性があることに少し不満を感じていると思います。書き込み/書き込みのデッドロックが大きな問題であることがわかりましたが、読み取りますか?ここでは銀行サイトを運営していません。毎回完璧な正確さを要求するわけではありません。

アイデア?考え?


すべての操作に対して新しいLINQtoSQL DataContextオブジェクトをインスタンス化していますか、それともすべての呼び出しで同じ静的コンテキストを共有していますか?

ジェレミー、ほとんどの場合、ベースコントローラーで1つの静的データコンテキストを共有しています。

private DBContext _db;
/// <summary>
/// Gets the DataContext to be used by a Request's controllers.
/// </summary>
public DBContext DB
{
    get
    {
        if (_db == null)
        {
            _db = new DBContext() { SessionName = GetType().Name };
            //_db.ExecuteCommand("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED");
        }
        return _db;
    }
}

すべてのコントローラーに対して、またはページごとに、または..より頻繁に新しいコンテキストを作成することをお勧めしますか?

4

22 に答える 22

44

MSDNによると:

http://msdn.microsoft.com/en-us/library/ms191242.aspx

READ COMMITTED SNAPSHOT または ALLOW SNAPSHOT ISOLATION データベース オプションが ON の場合、データベースで実行されたすべてのデータ変更の論理コピー (バージョン) が維持されます。特定のトランザクションによって行が変更されるたびに、データベース エンジンのインスタンスは、以前にコミットされた行のイメージのバージョンを tempdb に格納します。各バージョンには、変更を行ったトランザクションのトランザクション シーケンス番号が付けられています。変更された行のバージョンは、リンク リストを使用して連鎖されます。最新の行の値は常に現在のデータベースに格納され、tempdb に格納されているバージョン管理された行にチェーンされます。

実行時間の短いトランザクションの場合、変更された行のバージョンは、tempdb データベースのディスク ファイルに書き込まれることなく、バッファー プールにキャッシュされる場合があります。バージョン管理された行の必要性が短期間の場合、バッファー プールから削除されるだけで、必ずしも I/O オーバーヘッドが発生するとは限りません。

追加のオーバーヘッドによってパフォーマンスがわずかに低下するように見えますが、無視できる程度です。確認するためにテストする必要があります。

本当に必要でない限り、このオプションを設定して、コード クエリからすべての NOLOCK を削除してみてください。NOLOCK またはデータベース コンテキスト ハンドラーでグローバル メソッドを使用してデータベース トランザクションの分離レベルに対処することは、問題の応急処置です。NOLOCKS は、データ レイヤーの基本的な問題を覆い隠し、信頼性の低いデータを選択する可能性があります。自動選択/更新行のバージョン管理が解決策のようです。

ALTER Database [StackOverflow.Beta] SET READ_COMMITTED_SNAPSHOT ON
于 2008-08-21T20:53:51.283 に答える
37

NOLOCKREAD UNCOMMITTEDは滑りやすい斜面です。最初にデッドロックが発生する理由を理解していない限り、それらを使用しないでください。「すべての SQL クエリに with (nolock) を追加しました」と言うと心配になります。どこにでもWITH NOLOCKを追加する必要があるということは、データ層に問題があることを示しています。

update ステートメント自体は少し問題があるように見えます。トランザクションの早い段階でカウントを決定しますか、それとも単にオブジェクトから取得しますか? AnswerCount = AnswerCount+1質問が追加されたときは、おそらくこれを処理するためのより良い方法です。次に、正しいカウントを取得するためのトランザクションは必要ありません。また、潜在的にさらされている同時実行の問題について心配する必要もありません。

このタイプのデッドロックの問題を回避する簡単な方法の 1 つは、多くの作業を行わずにダーティ リードを有効にせずに"Snapshot Isolation Mode"(SQL 2005 の新機能) を使用することです。これにより、最後に変更されていないデータを常にクリーンに読み取ることができます。デッドロックしたステートメントを適切に処理したい場合は、かなり簡単にキャッチして再試行することもできます。

于 2008-08-21T15:23:10.173 に答える
25

OP の質問は、この問題が発生した理由を尋ねることでした。この投稿は、他の人が解決する可能性のある解決策を残しながら、それに答えることを望んでいます.

これはおそらくインデックス関連の問題です。たとえば、テーブル Posts に、ParentID と更新される 1 つ (または複数) のフィールド (AnswerCount、LastActivityDate、LastActivityUserId) を含む非クラスター化インデックス X があるとします。

SELECT コマンドがインデックス X で共有読み取りロックを実行して ParentId で検索し、クラスター化されたインデックスで共有読み取りロックを実行して残りの列を取得する必要がある場合、UPDATE コマンドで排他的書き込みを実行すると、デッドロックが発生します。クラスター化インデックスをロックし、インデックス X を更新するために書き込み専用ロックを取得する必要があります。

A が X をロックして Y を取得しようとしているのに対し、B は Y をロックして X を取得しようとしている状況があります。

もちろん、これが実際に原因であるかどうかを確認するために、OP がどのインデックスが使用されているかに関する詳細情報で投稿を更新する必要があります。

于 2008-09-16T19:19:01.077 に答える
18

私はこの質問とアテンダントの答えについてかなり不快です. 「この魔法の粉を試してみてください!その魔法の粉は違います!」がたくさんあります。

取得されたロックを分析し、デッドロックされているロックの正確なタイプを特定したことはどこにもありません。

あなたが示したのは、デッドロックではなく、いくつかのロックが発生したことだけです。

SQL 2005 では、以下を使用して、どのロックが解除されているかについての詳細情報を取得できます。

DBCC TRACEON (1222, -1)

デッドロックが発生したときに、より良い診断が得られるようにします。

于 2008-08-26T09:20:03.580 に答える
14

操作ごとに新しい LINQ to SQL DataContext オブジェクトをインスタンス化していますか? それとも、すべての呼び出しで同じ静的コンテキストを共有していますか? 私は最初に後者のアプローチを試みましたが、覚えている限り、DB で不要なロックが発生しました。ここで、アトミック操作ごとに新しいコンテキストを作成します。

于 2008-08-21T14:35:41.973 に答える
10

家を焼き払ってNOLOCKでハエを捕まえる前に、プロファイラーでキャプチャする必要のあるデッドロックグラフを確認することをお勧めします。

デッドロックには(少なくとも)2つのロックが必要であることを忘れないでください。接続1にはロックAがあり、ロックBが必要です。接続2の場合はその逆です。これは解決できない状況であり、誰かが与える必要があります。

これまでに示したことは、単純なロックによって解決されます。これは、SQLServerが一日中喜んで実行します。

あなた(またはLINQ)は、そのUPDATEステートメントを使用してトランザクションを開始し、事前に他の情報を選択していると思われます。ただし、実際には、デッドロックグラフをバックトラックして各スレッドによって保持されているロックを見つけ、次にプロファイラーをバックトラックしてそれらのロックが付与される原因となったステートメントを見つける必要があります。

このパズルを完了するには、少なくとも4つのステートメントがあると思います(または、複数のロックを取得するステートメント-おそらく投稿テーブルにトリガーがありますか?)。

于 2008-08-22T07:24:15.607 に答える
7

ユーザープロファイルが数秒古くなっていてもかまいませんか?

いいえ、それは完全に受け入れられます。基本トランザクション分離レベルを設定することは、おそらく最善/最もクリーンな方法です。

于 2008-08-21T14:22:43.563 に答える
5

典型的な読み取り/書き込みデッドロックは、インデックス順アクセスから発生します。読み取り (T1) は、インデックス A で行を特定し、インデックス B (通常はクラスター化) で射影された列を検索します。書き込み (T2) はインデックス B (クラスター) を変更し、インデックス A を更新する必要があります。 、パフ。T1が殺されました。これは、OLTP トラフィックが多く、インデックスが少し多すぎる環境でよく見られます :)。解決策は、読み取りが A から B にジャンプする必要がないようにする (つまり、A に列を含める、または射影されたリストから列を削除する) か、T2 が B から A にジャンプする必要がない (インデックス付きの列を更新しない) ようにすることです。残念ながら、ここでは linq はあなたの友達ではありません...

于 2009-05-15T19:37:19.670 に答える
3

READ_COMMITTED_SNAPSHOT を必ず on に設定する必要がありますが、これはデフォルトでは設定されていません。これにより、MVCC セマンティクスが得られます。これは、Oracle がデフォルトで使用するものと同じです。MVCC データベースがあると非常に便利ですが、使用しないのは非常識です。これにより、トランザクション内で次を実行できます。

USERS の更新 Set FirstName = 'foobar'; // 1 年間眠ることを決定します。

その間、上記をコミットしなくても、誰もがそのテーブルから問題なく選択を続けることができます。MVCC に慣れていない場合は、MVCC なしで生活できたことにショックを受けるでしょう。真剣に。

于 2008-08-25T18:45:37.473 に答える
3

デフォルトをコミットされていない読み取りに設定することはお勧めできません。間違いなく矛盾が生じ、現在よりも深刻な問題に直面することになります。スナップショット分離はうまく機能するかもしれませんが、Sql Server の動作方法が大幅に変更され、tempdbに大きな負荷がかかります。

(T-SQL で) try-catch を使用して、デッドロック状態を検出します。その場合は、クエリを再実行してください。これは、データベース プログラミングの標準的な方法です。

Paul Nielson のSql Server 2005 Bibleには、この手法の良い例があります。

これが私が使用する簡単なテンプレートです。

-- Deadlock retry template

declare @lastError int;
declare @numErrors int;

set @numErrors = 0;

LockTimeoutRetry:

begin try;

-- The query goes here

return; -- this is the normal end of the procedure

end try begin catch
    set @lastError=@@error
    if @lastError = 1222 or @lastError = 1205 -- Lock timeout or deadlock
    begin;
        if @numErrors >= 3 -- We hit the retry limit
        begin;
            raiserror('Could not get a lock after 3 attempts', 16, 1);
            return -100;
        end;

        -- Wait and then try the transaction again
        waitfor delay '00:00:00.25';
        set @numErrors = @numErrors + 1;
        goto LockTimeoutRetry;

    end;

    -- Some other error occurred
    declare @errorMessage nvarchar(4000), @errorSeverity int
    select    @errorMessage = error_message(),
            @errorSeverity = error_severity()

    raiserror(@errorMessage, @errorSeverity, 1)

    return -100
end catch;    
于 2008-08-21T23:37:14.247 に答える
3

@Jeff - 私は間違いなくこれに関する専門家ではありませんが、ほぼすべての呼び出しで新しいコンテキストをインスタンス化することで良い結果が得られました。ADO で呼び出しごとに新しい Connection オブジェクトを作成するのと似ていると思います。とにかく接続プールが引き続き使用されるため、オーバーヘッドは思ったほど悪くはありません。

次のようなグローバル静的ヘルパーを使用します。

public static class AppData
{
    /// <summary>
    /// Gets a new database context
    /// </summary>
    public static CoreDataContext DB
    {
        get
        {
            var dataContext = new CoreDataContext
            {
                DeferredLoadingEnabled = true
            };
            return dataContext;
        }
    }
}

そして、私はこのようなことをします:

var db = AppData.DB;

var results = from p in db.Posts where p.ID = id select p;

そして、更新についても同じことをします。とにかく、私はあなたほど多くのトラフィックを持っていませんが、ほんの一握りのユーザーで共有 DataContext を早い段階で使用したときに、ロックが発生していたことは間違いありません。保証はありませんが、試してみる価値はあります。

更新:次に、コードを見ると、その特定のコントローラー インスタンスの有効期間中のみデータ コンテキストを共有しています。これは、コントローラー内の複数の呼び出しによって何らかの方法で同時に使用されない限り、基本的には問題ないようです。このトピックに関するスレッドで、ScottGu 氏は次のように述べています。

コントローラーは単一のリクエストに対してのみ存在するため、リクエストの処理の最後にガベージコレクションが行われます (つまり、DataContext が収集されます)...

とにかく、それだけではないかもしれませんが、おそらくいくつかの負荷テストと組み合わせて、試してみる価値があるでしょう。

于 2008-08-21T14:50:34.450 に答える
3

Q.そもそもなぜテーブルに収納するAnswerCountのですか?Posts

Posts別のアプローチは、テーブルに を保存せずに、AnswerCount必要に応じて投稿への回答数を動的に計算することで、テーブルへの「書き戻し」をなくすことです。

はい、これは追加のクエリを実行していることを意味します。

SELECT COUNT(*) FROM Answers WHERE post_id = @id

またはより一般的に(ホームページにこれを表示している場合):

SELECT p.post_id, 
     p.<additional post fields>,
     a.AnswerCount
FROM Posts p
    INNER JOIN AnswersCount_view a
    ON <join criteria>
WHERE <home page criteria>

しかし、これは通常INDEX SCAN、 を使用するよりもリソースを効率的に使用することができますREAD ISOLATION

猫の皮をむく方法はたくさんあります。データベース スキーマの早期の非正規化は、スケーラビリティの問題を引き起こす可能性があります。

于 2008-08-22T10:12:06.187 に答える
2

過去に私にとってうまくいったことの 1 つは、すべてのクエリと更新が同じ順序でリソース (テーブル) にアクセスするようにすることです。

つまり、あるクエリが Table1、Table2 の順に更新され、別のクエリがそれを Table2、Table1 の順に更新すると、デッドロックが発生する可能性があります。

LINQ を使用しているため、更新の順序を変更できるかどうかはわかりません。しかし、それは見るものです。

于 2008-08-21T15:16:15.450 に答える
1

Jeremy の回答を見て、ベスト プラクティスはデータ操作ごとに新しい DataContext を使用することだと聞いたことを覚えていると思います。Rob Conery は DataContext に関するいくつかの投稿を書いており、シングルトンを使用するのではなく、常にそれらをニュースにしています。

Video.Show に使用したパターンは次のとおりです ( CodePlex のソース ビューへのリンク)。

using System.Configuration;
namespace VideoShow.Data
{
  public class DataContextFactory
  {
    public static VideoShowDataContext DataContext()
    {
        return new VideoShowDataContext(ConfigurationManager.ConnectionStrings["VideoShowConnectionString"].ConnectionString);
    }
    public static VideoShowDataContext DataContext(string connectionString)
    {
        return new VideoShowDataContext(connectionString);
    }
  }
}

次に、サービス レベル (更新の場合はさらに細かく) で:

private VideoShowDataContext dataContext = DataContextFactory.DataContext();

public VideoSearchResult GetVideos(int pageSize, int pageNumber, string sortType)
{
  var videos =
  from video in DataContext.Videos
  where video.StatusId == (int)VideoServices.VideoStatus.Complete
  orderby video.DatePublished descending
  select video;
  return GetSearchResult(videos, pageSize, pageNumber);
}
于 2008-08-21T16:40:18.523 に答える
1

ユーザー プロファイルが数秒古くても気にしますか?

数秒は間違いなく許容されます。とにかく、膨大な数の人々が同時に回答を送信しない限り、それほど長くはないようです.

于 2008-08-21T14:36:37.930 に答える
1

では、再試行メカニズムを実装する際の問題は何でしょうか? デッドロックが発生する可能性は常にあるので、それを特定して再試行するためのロジックがないのはなぜですか?

少なくとも他のオプションのいくつかは、再試行システムがめったに作動しないときに常に取られるパフォーマンスのペナルティを導入しませんか?

また、再試行が発生したときに何らかのログを記録することを忘れないでください。これにより、まれになることが頻繁に発生する状況に陥ることがありません。

于 2008-08-25T18:16:49.100 に答える
1

私はこれについてジェレミーに同意します。コントローラーごとに、またはページごとに新しいデータ コンテキストを作成する必要があるかどうかを尋ねられます。私は、独立したクエリごとに新しいデータ コンテキストを作成する傾向があります。

私は現在、あなたのように静的コンテキストを実装するために使用されるソリューションを構築しています.ストレステスト中にサーバーの獣(100万以上)に大量のリクエストを投げたとき、読み取り/書き込みロックもランダムに取得していました.

クエリごとに LINQ レベルで異なるデータ コンテキストを使用するように戦略を変更し、SQL サーバーが接続プールの魔法を機能させることができると信頼するとすぐに、ロックが消えたように見えました。

もちろん、私は時間的なプレッシャーにさらされていたので、同時に多くのことを試していたので、それが問題を解決したと 100% 確信することはできませんが、私には高いレベルの自信があります。 .

于 2008-08-21T15:02:30.647 に答える
1

ダーティ リードを実装する必要があります。

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

クエリで完全なトランザクション整合性を絶対に必要としない場合は、同時実行性の高いテーブルにアクセスするときにダーティ リードを使用する必要があります。あなたの Posts テーブルはそれらの 1 つになると思います。

これにより、コミットされていないトランザクションからのデータに対してクエリが実行される、いわゆる「ファントム読み取り」が発生する可能性があります。

ここでは銀行サイトを運営しているわけではありません。毎回完璧な精度は必要ありません。

ダーティ リードを使用します。完璧な精度が得られるわけではありませんが、デッドロックの問題は解消されるはずです。

それがなければ、作成するすべての LINQ 呼び出し (まあ、それらの大部分を占める単純な読み取り呼び出し) を 3 ~ 4 行のトランザクション コード ブロックでラップする必要がありますが、これは見苦しいものです。

「ベース データベース コンテキスト」でダーティ リードを実装する場合、トランザクションの整合性が必要な場合は、より高い分離レベルを使用して個々の呼び出しをいつでもラップできます。

于 2008-08-21T18:34:38.170 に答える
0

私はすべてを調整し続けます。ディスクサブシステムのパフォーマンスはどうですか? 平均ディスク キューの長さは? I/O がバックアップしている場合、実際の問題はデッドロックしているこれら 2 つのクエリではなく、システムのボトルネックとなっている別のクエリである可能性があります。チューニングされた 20 秒かかるクエリについて言及されましたが、他にもありますか?

実行時間の長いクエリを短縮することに集中してください。デッドロックの問題はなくなるでしょう。

于 2008-10-10T20:07:32.807 に答える
0

分離レベルをコミットされていない読み取りに設定しても、他のクエリに悪影響がない限り、Greg に同意する必要があります。

Jeff さん、データベース レベルで設定すると、次のようなクエリにどのような影響があるか知りたいです。

Begin Tran
Insert into Table (Columns) Values (Values)
Select Max(ID) From Table
Commit Tran
于 2008-08-21T14:33:57.843 に答える
0

同じ問題があり、サーバーで DTS が有効になっていないため (!)、TransactionScope で "IsolationLevel = IsolationLevel.ReadUncommitted" を使用できません。

それが私が拡張メソッドで行ったことです:

public static void SetNoLock(this MyDataContext myDS)
{
    myDS.ExecuteCommand("SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED");
}

そのため、重要な同時実行テーブルを使用する選択の場合、次のように「nolock」を有効にします。

using (MyDataContext myDS = new MyDataContext())
{
   myDS.SetNoLock();

   //  var query = from ...my dirty querys here...
}

提案は大歓迎です!

于 2008-10-28T13:00:26.193 に答える
0

私のプロフィールが数分でも古くなっても構わない。

失敗した後に読み取りを再試行していますか? 大量のランダム読み取りを実行すると、読み取れないときにいくつかがヒットする可能性は確かにあります。私が使用しているアプリケーションのほとんどは、読み取り数に比べて書き込み数が非常に少なく、読み取り数はあなたが得ている数にはほど遠いと確信しています。

「READ UNCOMMITTED」を実装しても問題が解決しない場合は、処理について詳しく知らずに解決するのは困難です。この動作に役立つチューニング オプションが他にもある可能性があります。MSSQL の第一人者が助けに来ない限り、ベンダーに問題を提出することをお勧めします。

于 2008-08-21T14:48:35.343 に答える