トランザクションを使用せず、回避策を使用して1つをシミュレートするように要求


43

私は数年間T-SQLを開発してきましたが、常に掘り下げて、言語のすべての側面についてできる限りのことを学び続けています。私は最近新しい会社で働き始め、取引に関して奇妙な提案だと思うものを受け取りました。それらを使用しないでください。代わりに、トランザクションをシミュレートする回避策を使用してください。これは、1つのデータベースで多くのトランザクションを処理し、その後多くのブロッキングを行うDBAによるものです。私が主に作業しているデータベースはこの問題の影響を受けず、トランザクションは過去に使用されたことがあります。

トランザクションはブロックすることが本来の性質であるため、トランザクションでブロックすることが期待されていることを理解しています。しかし、各ステートメントが正常に実行されなければならない場合が多くあります。1つが失敗した場合、それらはすべてコミットに失敗する必要があります。

私は常にトランザクションの範囲をできる限り狭く保ち、常にSET XACT_ABORT ONと組み合わせて使用​​し、常にTRY / CATCH内で使用しています。

例:

CREATE SCHEMA someschema;
GO


CREATE TABLE someschema.tableA
(id   INT NOT NULL IDENTITY(1, 1) PRIMARY KEY, 
 ColA VARCHAR(10) NOT NULL
);
GO

CREATE TABLE someschema.tableB
(id   INT NOT NULL IDENTITY(1, 1) PRIMARY KEY, 
 ColB VARCHAR(10) NOT NULL
); 
GO


CREATE PROCEDURE someschema.ProcedureName @ColA VARCHAR(10), 
                                          @ColB VARCHAR(10)
AS
SET NOCOUNT, XACT_ABORT ON;
BEGIN
BEGIN TRY
    BEGIN TRANSACTION;

    INSERT INTO someschema.tableA(ColA)
    VALUES(@ColA);

    INSERT INTO someschema.tableB(ColB)
    VALUES(@ColB);

--Implement error
    SELECT 1/0 

    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    IF @@trancount > 0
    BEGIN
        ROLLBACK TRANSACTION;
    END;
    THROW;
    RETURN;
END CATCH;
END;
GO

ここに彼らが私がすることを提案したものがあります。

GO



CREATE PROCEDURE someschema.ProcedureNameNoTransaction @ColA VARCHAR(10), 
                                                       @ColB VARCHAR(10)
AS
SET NOCOUNT ON;
BEGIN
BEGIN TRY
    DECLARE @tableAid INT;
    DECLARE @tableBid INT;

    INSERT INTO someschema.tableA(ColA)
    VALUES(@ColA);
    SET @tableAid = SCOPE_IDENTITY();

    INSERT INTO someschema.tableB(ColB)
    VALUES(@ColB);
    SET @tableBid = SCOPE_IDENTITY();

--Implement error
    SELECT 1/0 

END TRY
BEGIN CATCH
    DELETE FROM someschema.tableA
    WHERE id = @tableAid;

    DELETE FROM someschema.tableB
    WHERE id = @tableBid;

    THROW;

    RETURN;
END CATCH;
END;
GO

コミュニティへの私の質問は次のとおりです。これは、トランザクションの実行可能な回避策として意味がありますか?

私がトランザクションについて知っていること、およびソリューションが提案していることからの私の意見は、いいえ、これは実行可能なソリューションではなく、多くの障害点をもたらします。

推奨される回避策では、4つの暗黙的なトランザクションが発生しています。tryでの2つの挿入と、catchでの削除のためのさらに2つのトランザクション。挿入を「元に戻す」が、何もロールバックせず、実際には何もロールバックされない。

これは、彼らが提案している概念を示すための非常に基本的な例です。この例の複数の結果セットと2つのパラメーター値の「ロールバック」が想像できるように非常に複雑になるため、私がこれを行ってきた実際のストアドプロシージャのいくつかは、それらを徹底的に長く管理しにくくしています。「ロールバック」は現在手動で行われているため、本物だから何かを見逃す機会があります。

私が存在すると思う別の問題は、タイムアウトまたは切断された接続に関するものです。これはまだロールバックされますか?これは、これらの場合にトランザクションがロールバックされるように、SET XACT_ABORT ONを使用する理由についての私の理解です。

事前にフィードバックいただきありがとうございます!


4
定められた目的を果たさないコメントは削除されたか、コミュニティWikiの回答に移動しました。
ポール・ホワイト

回答:


61

SQL Server(およびおそらく他の適切なRDBMS)でトランザクションを使用することはできませ。明示的なトランザクション境界(begin transaction... commit)がない場合、各SQLステートメントは新しいトランザクションを開始し、ステートメントが完了(または失敗)した後、暗黙的にコミット(またはロールバック)されます。

「DBA」として自分自身を提示する人によって提案されたトランザクションシミュレーションは、「ソフト」エラーのみに対処し、「ハード」エラーを処理できないため、トランザクション処理の4つの必須プロパティのうち3つを保証できません。ネットワークの切断、停電、ディスク障害など。

  • アトミック性:失敗。疑似トランザクションの途中で「ハード」エラーが発生した場合、変更は非アトミックになります。

  • 一貫性:失敗します。上記のことから、「ハード」エラーの後、データは一貫性のない状態になります。

  • 分離:失敗。同時の疑似トランザクションが完了する前に、疑似トランザクションによって変更されたデータの一部を変更する可能性があります。

  • 耐久性:成功。あなたが作る変更します耐久性で、データベース・サーバがそれを保証します。これは、同僚のアプローチが台無しにできない唯一のものです。

ロックは、あらゆる種類またはRDBMSのトランザクションのACIDityを保証するために広く使用され、経験的に成功している方法です(このサイトは例です)。ランダムなDBAが、過去50年間で興味深いデータベースシステムを構築してきた数百、場合によっては数千のコンピューター科学者やエンジニアよりも、並行性の問題に対するより良い解決策を思い付く可能性は非常に低いと思います。60年?(私はこれが「権威への訴え」の議論としてやや誤解していることを認識しているが、私は関係なくそれに固執する。)

結論として、可能であれば「DBA」のアドバイスを無視し、精神があればそれと戦い、特定の並行性の問題が発生した場合はここに戻ってください。


14

CATCHブロックに入らないほど深刻なエラーがいくつかあります。ドキュメントから

セッションのSQL Serverデータベースエンジンのタスク処理を停止する、重大度が20以上のエラー。重大度が20以上のエラーが発生し、データベース接続が中断されない場合、TRY ... CATCHがエラーを処理します。

クライアント割り込み要求や壊れたクライアント接続などの注意。

システム管理者がKILLステートメントを使用してセッションを終了したとき。

...

バッチの実行を妨げる構文エラーなどのコンパイルエラー。

発生するエラー...遅延名前解決のため。

これらの多くは、動的SQLを使用して簡単に作成できます。示したような取り消しステートメントは、そのようなエラーからデータを保護しません。


2
正しい-そして、他に何もなければ、コードの実行中にクライアントが死ぬと、「CATCHブロックが入力されないほど深刻」というエラーになります。ソフトウェアをどれだけ信頼していても(自分のコードだけでなく、関連するすべてのソフトウェアスタックのすべての部分)、常にハードウェア障害が発生する可能性があります(再び、チェーンのどこかで) 。これを念頭に置くことは、この種の「回避策」につながる親切な考え方に対する良い防御策です。
dgould

2
また、あなたはデッドロックの犠牲者になるかもしれません。CATCHブロックは実行されますが、データベースに書き込もうとするとスローされます。
ジョシュア

10

i-one:推奨される回避策により、(少なくとも) ACIDの「A」に違反する可能性があります。たとえば、SPがリモートクライアントによって実行され、接続が切断されると、サーバーが2つの挿入/削除の間のセッションを終了できるため、部分的な「コミット」/「ロールバック」が発生する場合があります。

これは、トランザクションの実行可能な回避策として意味がありますか?

dan-guzman:いいえ、CATCHクライアントAPIがバッチをキャンセルしたため、クエリタイムアウトの場合、ブロックは実行されません。トランザクションがなければSET XACT_ABORT ON、現在のステートメント以外はロールバックできません。

tibor-karaszi:トランザクションが4つあります。これは、トランザクションログファイルにより多くのログを記録することを意味します。各トランザクションでは、その時点までにログレコードの同期書き込みが必要であることに注意してください。つまり、多くのトランザクションを使用すると、その側面からもパフォーマンスが低下します。

rbarryyoung:大量のブロッキングが発生している場合、データ設計を修正するか、テーブルアクセス順序を合理化するか、より適切な分離レベルを使用する必要があります。彼らは、彼らの問題(そしてそれを理解できないこと)があなたの問題になると仮定しています。他の何百万ものデータベースからの証拠はそうではないということです。

また、彼らが手動で実装しようとしているのは、事実上、貧しい人々の楽観的な並行性です。代わりにすべきことは、既にSQL Serverに組み込まれている、世界で最高の楽観的同時実行性を使用することです。これは上記の分離ポイントに行きます。おそらく、彼らは現在使用している悲観的並行性分離レベルから、楽観的並行性分離レベルのSNAPSHOTいずれかに切り替える必要があります READ_COMMITTED_SNAPSHOT。これらは、正しく実行することを除いて、手動コードと同じことを効果的に行います。

ross-presser:非常に長時間実行されるプロセスがある場合-今日と来週に何かが起こるように、何かがフォローアップする必要があり、来週の事柄が失敗した場合、今日の事柄は遡及的に失敗しなければならない-あなたはサガを調べたいかもしれません。厳密に言えば、これはサービスバスを必要とするため、データベースの外部にあります。


5

悪いアイデアのコードは、最終的に修正するのにもっと高価になります。

明示的なトランザクション(ロールバック/コミット)を使用してブロッキングの問題がある場合は、問題に対処するための優れたアイデアを得るためにDBAをインターネットに接続してください。

ブロックを緩和する方法を次に示します。https : //www.sqlservercentral.com/articles/using-indexes-to-reduce-blocking-in-concurrent-transactions

インデックスは、行/行のセットを見つけるためにテーブル/ページで発生する必要があるシークの数を減らします。これらは一般に、SELECT *クエリの実行時間を短縮する方法と見なされています。それらは、多数の更新に関係するテーブルに適しているとは見なされません。実際、これらの場合、UPDATEクエリの完了にかかる時間が長くなるため、INDEXESは好ましくないことがわかります。

しかし、これは常にそうではありません。UPDATEステートメントの実行を少し深く掘り下げると、最初にSELECTステートメントを実行することも必要であることがわかります。これは、クエリが相互に排他的な行のセットを更新する特別でよく見られるシナリオです。ここでのインデックスは、一般的な考えに反して、データベースエンジンのパフォーマンスを大幅に向上させる可能性があります。


4

偽のトランザクション戦略は、トランザクションが特に防止する並行性の問題を許容するため、危険です。2番目の例では、ステートメント間でデータが変更される可能性があることを考慮してください。

偽のトランザクションの削除は、実行または成功することを保証されていません。偽のトランザクション中にデータベースサーバーの電源が切れると、すべてではありませんが一部の影響が残ります。また、トランザクションのロールバックと同様の方法で成功することも保証されていません。

この戦略は挿入では機能する可能性がありますが、更新や削除では機能しません(タイムマシンSQLステートメントは使用できません)。

厳密なトランザクションの同時実行がブロッキングを引き起こしている場合、保護レベルを低下させるものも含め、多くの解決策があります...これらは問題を解決する正しい方法です。

DBAは、データベースのユーザーが1人だけの場合は問題なく動作する可能性があるソリューションを提供していますが、あらゆる種類の深刻な使用には絶対に向いていません。


4

これはプログラミングの問題ではなく、対人/コミュニケーションの問題です。ほとんどの場合、「DBA」はトランザクションではなくロックを心配しています。

他の回答では、なぜトランザクションを使用する必要があるのか​​を説明しています...適切に使用されたトランザクションがなければデータの整合性がないため、RDBMSが行うことを意味します。そのため、実際の問題の解決方法に焦点を当てます。あなたの「DBA」は取引に対するアレルギーを発症し、彼に考えを変えるよう説得しました。

この男は「悪いコードがひどいパフォーマンスをもたらす特定のシナリオ」と「すべてのトランザクションが悪い」を混同していると思います。有能なDBAがその間違いを犯すとは思わないので、それは本当に奇妙です。ひどいコードで本当に悪い経験をしたのでしょうか?

次のようなシナリオを考えてみましょう。

BEGIN
UPDATE or DELETE some row, which takes locks it
...do something that takes a while
...perform other queries
COMMIT

このスタイルのトランザクション使用では、ロック(または複数のロック)が保持されます。つまり、同じ行にヒットする他のトランザクションは待機する必要があります。ロックが長時間保持されている場合、特に他の多くのトランザクションが同じ行をロックしたい場合、これは本当にパフォーマンスを損なう可能性があります。

トランザクションを使用しないという奇妙に間違った考えを持っている理由、問題のあるクエリの種類などを彼に尋ねることができます。パフォーマンス、彼を安心させるなど。

彼があなたに言っていることは、「ドライバーに触れないでください!」です。そのため、質問に投稿したコードは、基本的にハンマーを使用してネジを駆動しています。より良いオプションは、ドライバーの使用方法を知っていることを彼に納得させることです...

私はいくつかの例を考えることができます...まあ、それらはMySQL上にありましたが、それもうまくいくはずです。

全文索引の更新に時間がかかったフォーラムがありました。ユーザーが投稿を送信すると、トランザクションはトピックテーブルを更新して投稿数と最終投稿日を増やし(トピック行をロックします)、投稿を挿入し、トランザクションはフルテキストインデックスの更新が完了するまでロックを保持しますそして、コミットが行われました。

RAMが少なすぎるrustbucketで実行されたため、フルテキストインデックスを更新すると、ボックス内の単一の低速回転ドライブで数秒の激しいランダムIOが頻繁に発生しました。

問題は、トピックをクリックしたユーザーがクエリを実行してトピックのビュー数を増やすことであり、これにはトピック行のロックも必要でした。したがって、フルテキストインデックスが更新されている間、誰もトピックを表示できませんでした。つまり、行は読み取れますが、更新するとロックされます。

さらに悪いことに、投稿は親フォーラムテーブルの投稿数を更新し、フルテキストインデックスが更新されている間もロックを保持します...これにより、フォーラム全体が数秒間フリーズし、大量のリクエストがWebサーバーキューに蓄積されました。 。

解決策は、正しい順序でロックを取得することでした。BEGIN、投稿を挿入し、ロックを取得せずにフルテキストインデックスを更新し、投稿数と最終投稿日、COMMITでトピック/フォーラム行をすばやく更新しました。これで問題は完全に解決しました。いくつかのクエリを移動するだけで、本当に簡単でした。

この場合、トランザクションは問題ではありませんでした...長時間の操作の前に不要なロックを取得していました。トランザクションでロックを保持する際に避けるべき他の例:ユーザーの入力を待つ、低速の回転ドライブ、ネットワークIOなどからキャッシュされていない多くのデータにアクセスする

もちろん、選択の余地がなく、面倒なロックを保持しながら長時間の処理を行わなければならない場合があります。これにはトリックがあります(データのコピーを操作するなど)が、パフォーマンスのボトルネックは意図的に取得されていないロックが原因であることが多く、クエリを並べ替えるだけで問題が解決します。さらに良いことは、クエリの作成中にロックがかかっていることを認識していることです...

他の答えは繰り返しませんが、実際には...トランザクションを使用します。問題は、データベースの最も重要な機能を回避するのではなく、「DBA」を納得させることです...


3

TLDR:適切な分離レベルを使用します

トランザクションを使用せず、「手動」リカバリを使用したアプローチは非常に複雑になる可能性があります。複雑度が高いということは、通常、それを実装するのにより多くの時間とエラーを修正するのにより多くの時間を意味します(複雑さは実装でより多くのエラーにつながるため)。それは、そのようなアプローチが顧客により多くのコストをかける可能性があることを意味します。

「dba」同僚の主な関心事はパフォーマンスです。これを改善する方法の1つは、適切な分離レベルを使用することです。何らかの種類の概要データをユーザーに提供する手順があるとします。このような手順では、SERIALIZABLE分離レベルを必ずしも使用する必要はありません。多くの場合、READ UNCOMMITTEDで十分です。つまり、このような手順は、一部のデータを作成または変更するトランザクションによってブロックさません

データベース内の既存のすべての関数/手順を確認し、それぞれの妥当な分離レベルを評価し、顧客にパフォーマンス上の利点を説明することをお勧めします。次に、これらの機能/手順を適宜調整します。


2

また、インメモリOLTPテーブルを使用することもできます。もちろん、それらはまだトランザクションを使用しますが、ブロックは関係しません。
すべての操作をブロックする代わりに成功しますが、コミットフェーズ中にエンジンがトランザクションの競合をチェックし、コミットの1つが失敗する場合があります。マイクロソフトでは、「オプティミスティックロック」という用語を使用しています。
同じ行を更新しようとする2つの同時トランザクションなど、2つの書き込み操作間の競合がスケーリングの問題の原因である場合、インメモリOLTPは1つのトランザクションを成功させ、他のトランザクションを失敗させます。失敗したトランザクションは、明示的または暗黙的に再送信して、トランザクションを再試行する必要があります。
詳細:メモリ内OLTP


-5

限られた範囲でトランザクションを使用する方法はありますが、それはデータモデルをよりオブジェクト指向に変更することです。そのため、たとえば人物に関する人口統計データを複数のテーブルに保存して相互に関連付けてトランザクションを要求するのではなく、その人物について知っているすべてを単一のフィールドに保存する単一のJSONドキュメントを作成できます。もちろん、ドメインの範囲をはるかに超えることは、設計上のもう1つの課題であり、DBAではなく開発者が行うのが最適です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.