私は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つの暗黙的なトランザクションが発生しているのがわかります。試行の2つの挿入と、キャッチの削除のための2つのトランザクション。挿入は「元に戻す」が、何もロールバックしないため、実際には何もロールバックされない。
これは、彼らが提案している概念を示す非常に基本的な例です。この例では、複数の結果セットと2つのパラメーター値を「ロールバック」することは想像どおりに非常に複雑になるため、私がこれを行っている実際のストアドプロシージャの一部は、非常に長く、管理が困難です。 「ロールバック」は現在手動で行われているため、本物だから何かを見逃す機会があります。
もう1つの問題は、タイムアウトまたは接続の切断です。これはまだロールバックされますか?これは、これらのケースでトランザクションがロールバックされるようにSET XACT_ABORT ONを使用する必要がある理由についての私の理解です。
事前にフィードバックをありがとう!
not SQL Server(およびおそらく他の適切なRDBMS)でトランザクションを使用することはできません。明示的なトランザクション境界がない場合(begin transaction
... commit
)各SQLステートメントは新しいトランザクションを開始します。新しいトランザクションは、ステートメントが完了(または失敗)した後に暗黙的にコミット(またはロールバック)されます。
「DBA」として自分自身を提示する人が提案するトランザクションシミュレーションは、「ソフト」エラーのみに対処し、「ハード」エラーを処理できないため、トランザクション処理の4つの必須プロパティのうち3つを保証できません。ネットワークの切断、停電、ディスク障害など。
原子性:失敗。 「ハード」エラーが疑似トランザクションの途中で発生した場合、変更は不可分になります。
一貫性:失敗。上記の結果から、「ハード」エラーの後にデータが不整合な状態になることがわかります。
分離:失敗。並行する疑似トランザクションがsomeを変更して、疑似トランザクションが変更する前に、疑似トランザクションによって変更される可能性があります。
耐久性:成功。加えた変更willは永続的であり、データベースサーバーはそれを保証します。これは、同僚のアプローチが台無しにできない唯一のものです。
ロックは広く使用されている経験的に成功した方法であり、あらゆる種類のトランザクションまたはRDBMSでトランザクションのACIDityを保証します(このサイトは一例です)。ランダムなDBAが、過去数年間に興味深いデータベースシステムを構築してきた数百、おそらく数千のコンピュータサイエンティストやエンジニアよりも、同時実行性の問題に対する優れたソリューションを思いつくことはほとんどありません。 60年? (これは「権威への訴え」の議論としてはやや誤りですが、私はそれに固執します。)
結論として、可能な場合は「DBA」のアドバイスを無視し、精神があればそれと戦い、特定の同時実行性の問題が発生した場合はここに戻ってください。
CATCHブロックに入ることができないほど深刻なエラーがいくつかあります。 ドキュメント から
セッションのSQL Serverデータベースエンジンタスクの処理を停止する重大度が20以上のエラー。重大度が20以上のエラーが発生し、データベース接続が中断されていない場合、TRY ... CATCHがエラーを処理します。
クライアント割り込み要求やクライアント接続の切断などの注意。
システム管理者がKILLステートメントを使用してセッションを終了したとき。
...
構文エラーなど、バッチの実行を妨げるコンパイルエラー。
名前解決の遅延が原因で発生するエラー...
これらの多くは、動的SQLを使用して簡単に作成できます。あなたが示したような取り消し文はそのようなエラーからデータを保護しません。
i-one:推奨される回避策により、(少なくとも)-の「A」に違反することが可能になります [〜#〜]酸[〜#〜] 。たとえば、SPがリモートクライアントによって実行されており、接続が切断されている場合、サーバーが 終了 セッションであるため、部分的な「コミット」/「ロールバック」が発生する可能性があります。 2つの挿入/削除の間(および中止SP実行が終了する前に実行)。
これはトランザクションの実行可能な回避策として意味がありますか?
dan-guzman:いいえ、クエリタイムアウトの場合、CATCH
ブロックは実行されません。クライアントAPIがバッチをキャンセルしました。トランザクションがないと、SET XACT_ABORT ON
は現在のステートメント以外をロールバックできません。
tibor-karaszi:4つのトランザクションがあり、トランザクションログファイルへのログ記録が増えます。各トランザクションでは、その時点までのログレコードの同期書き込みが必要であることを忘れないでください。つまり、多くのトランザクションを使用すると、その側面からもパフォーマンスが低下します。
rbarryyoung:多くのブロッキングが発生している場合、データ設計を修正するか、テーブルのアクセス順序を合理化する必要があります、またはより適切な分離レベルを使用します。彼らは彼らの問題(そしてそれを理解できないこと)があなたの問題になると想定しています。他の何百万ものデータベースからの証拠は、そうではないということです。
また、彼らが手動で実装しようとしているのは、実質的に貧乏人の楽観的同時実行です。代わりに、SQL Serverに既に組み込まれている世界最高の楽観的同時実行制御を使用する必要があります。これは上記の分離ポイントに行きます。おそらく、現在使用しているペシミスティック並行性分離レベルから、楽観的並行性分離レベルSNAPSHOT
またはREAD_COMMITTED_SNAPSHOT
のいずれかに切り替える必要があります。これらは、それが正しく実行されることを除いて、手動のコードと実質的に同じことを実行します。
ross-presser :非常に長い実行中のプロセスがある場合-今日と次の週に何かが起こるような何かがフォローアップする必要があり、来週のことが失敗した場合、今日は遡って失敗する必要があります- sagas を調べます。厳密に言うと、サービスバスが必要なため、これはデータベースの範囲外です。
悪い考えのコードは、将来、修正するのによりコストがかかるだけです。
明示的なトランザクション(ロールバック/コミット)を使用してブロッキングの問題がある場合は、DBAにインターネットを参照して、問題に対処するための優れたアイデアを求めてください。
ブロッキングを軽減する方法を次に示します。 https://www.sqlservercentral.com/articles/using-indexes-to-reduce-blocking-in-concurrent-transactions
インデックスは、行/行のセットを見つけるためにテーブル/ページで発生しなければならないシークの数を減らします。これらは一般に、SELECT *クエリの実行時間を短縮する方法と見なされています。これらは、多数のUPDATESに関与するテーブルには適していないと見なされます。実際、INDEXESは、UPDATEクエリの完了にかかる時間を増加させるため、これらの場合には好ましくないことがわかっています。
しかし、これは常に当てはまるわけではありません。 UPDATEステートメントの実行を少し深く掘り下げると、最初にSELECTステートメントを実行する必要があることもわかります。これは、クエリが相互に排他的な行のセットを更新する、よく見られる特別なシナリオです。ここのINDEXESは、一般的な考えに反して、データベースエンジンのパフォーマンスを大幅に向上させることができます。
偽のトランザクション戦略は、トランザクションが特に防止する同時実行性の問題を許容するため、危険です。 2番目の例では、ステートメント間でデータが変更される可能性があることを考慮してください。
偽のトランザクションの削除は、実行または成功することが保証されていません。偽のトランザクション中にデータベースサーバーがオフになった場合、影響のすべてではなく一部が残ります。また、トランザクションのロールバックが成功することも保証されていません。
この戦略は挿入では機能しますが、更新または削除では機能しません(タイムマシンSQLステートメントはありません)。
厳密なトランザクションの同時実行が原因でブロッキングが発生している場合、多くのソリューションがあり、保護レベルを下げるものも含まれます...これらは問題を解決する正しい方法です。
あなたのDBAは、データベースのユーザーが1人しかいない場合でも問題なく機能するソリューションを提供していますが、あらゆる種類の深刻な使用にはまったく適していません。
これはプログラミングの問題ではなく、対人関係/コミュニケーションの問題です。ほとんどの場合、「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にありましたが、それもうまくいくはずです。
全文索引の更新にしばらく時間がかかるフォーラムがありました。ユーザーが投稿を送信すると、トランザクションはトピックテーブルを更新して投稿数と最終投稿日を増やし(トピック行をロックする)、投稿を挿入し、フルテキストインデックスの更新が完了するまでトランザクションはロックを保持しますそしてCOMMITが行われました。
RAMが少なすぎるrustbucketで実行されたため、フルテキストインデックスを更新すると、ボックス内の単一の低速回転ドライブで数秒の激しいランダムIOが発生することがよくありました。
問題は、トピックをクリックしたユーザーがクエリを実行してトピックのビュー数を増やし、トピック行のロックも必要になることでした。したがって、フルテキストインデックスの更新中は、誰もトピックを表示できませんでした。つまり、行を読み取ることはできますが、更新するとロックされます。
さらに悪いことに、投稿すると、親フォーラムテーブルの投稿数が更新され、フルテキストインデックスが更新されている間もロックが保持されます。これにより、フォーラム全体が数秒間凍結され、大量のリクエストがWebサーバーキューに蓄積されます。 。
ソリューションは、正しい順序でロックを取得することでした:BEGIN、投稿を挿入し、ロックを取得せずにフルテキストインデックスを更新してから、トピック/フォーラムの行を投稿数と最終投稿日、およびCOMMITですばやく更新しました。それで問題は完全に解決しました。いくつかのクエリを移動するだけで、非常に単純でした。
この場合、トランザクションは問題ではありませんでした...長い操作の前に不要なロックを取得することでした。トランザクションでロックを保持しているときに避けるべきその他の例:ユーザー入力の待機、低速回転ドライブからのキャッシュされていない大量のデータへのアクセス、ネットワークIOなど。
もちろん、場合によっては、選択の余地がなく、扱いにくいロックを保持しながら長い処理を行わなければなりません。これにはトリックがあります(データのコピーを操作するなど)が、パフォーマンスのボトルネックは、意図的に取得されなかったロックが原因であることが多く、クエリを並べ替えるだけで問題が解決します。さらに良いことに、クエリの作成中に取得されたロックを認識しています...
他の答えは繰り返さないが、トランザクションを使用する。あなたの問題は、「DBA」を説得することであり、データベースの最も重要な機能を回避していない...
TLDR:適切な分離レベルを使用します。
トランザクションを使用しないアプローチと「手動」リカバリを使用するアプローチは、非常に複雑になる場合があります。複雑さが高いということは、通常、それを実装するのにはるかに多くの時間と、エラーを修正するのにはるかに多くの時間を意味します(複雑さが実装のエラーを増やすため)。それは、そのようなアプローチはあなたの顧客にはるかに多くのコストをかけることを意味します。
「dba」の同僚の主な関心事はパフォーマンスです。これを改善する方法の1つは、適切な分離レベルを使用することです。ある種の概要データをユーザーに提供する手順があるとします。このような手順では、必ずしもシリアライズ可能分離レベルを使用する必要はありません。多くの場合、READ UNCOMMITTEDで十分です。つまり、そのような手順は、一部のデータを作成または変更するトランザクションによってブロックされません。
データベース内の既存のすべての関数/手順を確認し、それぞれの適切な分離レベルを評価して、パフォーマンスの利点を顧客に説明することをお勧めします。次に、これらの関数/手順を適宜調整します。
インメモリOLTPテーブルを使用することもできます。もちろん、これらのテーブルはまだトランザクションを使用していますが、ブロックは含まれていません。
ブロックする代わりにすべての操作は成功しますが、コミットフェーズ中にエンジンはトランザクションの競合をチェックし、コミットの1つが失敗する可能性があります。マイクロソフトでは「楽観的ロック」という用語を使用しています。
同じ行を更新しようとする2つの同時トランザクションなど、2つの書き込み操作間の競合がスケーリングの問題の原因である場合、インメモリOLTPを使用すると、1つのトランザクションが成功し、失敗します。失敗したトランザクションは、明示的または暗黙的に再送信して、トランザクションを再試行する必要があります。
詳細: メモリ内OLTP