データベースは、トランザクションのどの時点でインデックスを更新しますか?


11

インデックスとトランザクションの両方が関与する挿入のイベントのシーケンスを理解しようとしています。

たとえば、Oracleのドキュメントには次のように記載されています。

データをロードする前に1つ以上のインデックスを作成[または保持]している場合、データベースは各行が挿入されるたびにすべてのインデックスを更新する必要があります。

しかし、トランザクションを作成し、5つの行を挿入してからコミットするとどうなりますか?挿入ごとにインデックスが更新されますか、それともコミットポイントで更新されますか?

これらのレコードがコミットされるまで、更新されたインデックスはおそらく使用できないため、ロジックはそれらがコミットポイントでのみ更新されることを教えてくれます。しかし、それは本当ですか?

もしそうなら、挿入する行が1mある場合、最高のパフォーマンスを得るには、100kレコードの10トランザクションではなく、すべての行の1つの大きなコミットを実行する必要がありますか?もちろん、これは、999,999行が失敗した場合にロールバックが大きくなるリスクがあることを理解しています。

私の専門用語が少し出ていればお詫びします。私は貿易ではDBAではありません。私は特定のデータベースにはあまり関心がありません。データベース全般についてですが、私が最もよく使用しているのはOracleとPostgresです。私はこのトピックについて検索しましたが、決定的な答えは本当に見つかりません。

回答:


8

SQL ServerとOracleを使用しています。おそらくいくつかの例外がありますが、それらのプラットフォームの一般的な答えは、データとインデックスが同時に更新されるということです。

トランザクションを所有するセッションと他のセッションでインデックスが更新されるタイミングを区別すると役立つと思います。デフォルトでは、トランザクションがコミットされるまで、他のセッションには更新されたインデックスが表示されません。ただし、トランザクションを所有するセッションには、更新されたインデックスがすぐに表示されます。

それについて考える1つの方法として、主キーを持つテーブルを検討してください。SQL ServerおよびOracleでは、これはインデックスとして実装されます。ほとんどの場合INSERT、主キーに違反するような処理が行われるとすぐにエラーが発生します。そのためには、データと同時にインデックスを更新する必要があります。Postgresなどの他のプラットフォームでは、トランザクションがコミットされたときにのみチェックされる遅延制約が許可されることに注意してください。

次に、一般的なケースを示す簡単なOracleデモを示します。

CREATE TABLE X_TABLE (PK INT NULL, PRIMARY KEY (PK));

INSERT INTO X_TABLE VALUES (1);
INSERT INTO X_TABLE VALUES (1); -- no commit

2番目のINSERTステートメントはエラーをスローします。

SQLエラー:ORA-00001:一意制約(XXXXXX.SYS_C00384850)違反

00001. 00000-"一意の制約(%s。%s)に違反しています"

*原因:UPDATEまたはINSERTステートメントが重複キーを挿入しようとしました。DBMS MACモードで構成されたTrusted Oracleの場合、重複するエントリが別のレベルに存在すると、このメッセージが表示されることがあります。

*アクション:一意の制限を削除するか、キーを挿入しないでください。

以下のインデックス更新アクションを見たい場合は、SQL Serverの簡単なデモです。まず、100万行と列の非クラスター化インデックスを含む2列のテーブルを作成しVALます。

DROP TABLE IF EXISTS X_TABLE_IX;

CREATE TABLE X_TABLE_IX (
ID INT NOT NULL,
VAL VARCHAR(10) NOT NULL
PRIMARY KEY (ID)
);

CREATE INDEX X_INDEX ON X_TABLE_IX (VAL);

-- insert one million rows with N from 1 to 1000000
INSERT INTO X_TABLE_IX
SELECT N, N FROM dbo.Getnums(1000000);

次のクエリは非クラスタ化インデックスを使用できます。これは、インデックスがそのクエリのカバーインデックスであるためです。実行に必要なすべてのデータが含まれています。予想どおり、返品は返されません。

SELECT *
FROM X_TABLE_IX
WHERE VAL = 'A';

クエリ1

次に、トランザクションを開始VALして、テーブルのほとんどすべての行を更新します。

BEGIN TRANSACTION

UPDATE X_TABLE_IX
SET VAL = 'A'
WHERE ID <> 1;

以下はそのためのクエリプランの一部です。

クエリ2

赤で囲まれた部分は、非クラスター化インデックスの更新です。青色で囲まれているのは、本質的にテーブルのデータであるクラスター化インデックスの更新です。トランザクションがコミットされていなくても、クエリの実行の一部でデータとインデックスが更新されていることがわかります。関連するデータのサイズとその他の要因によっては、これが計画に常に表示されるわけではないことに注意してください。

トランザクションがまだコミットされていない状態でSELECT、上からクエリをもう一度見てみましょう。

SELECT *
FROM X_TABLE_IX
WHERE VAL = 'A';

ここに画像の説明を入力してください

クエリオプティマイザーは引き続きインデックスを使用でき、今回は999999行が返されると推定します。クエリを実行すると、予期した結果が返されます。

これは簡単なデモでしたが、うまくいけば、少しはうまくいきました。

余談ですが、インデックスがすぐに更新されないと主張される可能性のあるいくつかのケースを知っています。これはパフォーマンス上の理由で行われ、エンドユーザーは一貫性のないデータを見ることができないはずです。たとえば、SQL Serverのインデックスに削除が完全に適用されない場合があります。バックグラウンドプロセスが実行され、最終的にデータがクリーンアップされます。興味があれば、ゴーストレコードについて読むことができます。


これはすばらしい答えです。また、私が疑問に思っていたもう1つのことにも答えます。主キー(または同様の)違反がInsertまたはCommitのどちらで発生するかです。そのような完全な応答をありがとう。
マークアイルランド

関連する質問(制約違反がいつ発生するかについて)は、遅延トランザクションを使用するかどうかに関連しています。たとえば、SQL Serverは遅延トランザクションを実装していないため、すべての違反はステートメントの最後で発生します。他のDBMSには(たとえば、すべてのタイプの制約があるわけではありませんが)Postgresがあるため、制約が延期されると、トランザクションのコミットフェーズで違反がチェックされます)。
ypercubeᵀᴹ

オラクルは遅延制約もサポートしています
BobC

1

私の経験では、1,000,000行の挿入は実際にはより多くのリソースを必要とし、バッチ挿入を使用した場合よりも完了するまでに時間がかかります。これは、例として、10,000行の100挿入に実装できます。

これにより、挿入されるバッチのオーバーヘッドが削減され、バッチが失敗した場合のロールバックは小さくなります。

いずれの場合でも、SQL Serverには、バッチ挿入を実行するために使用できるbcpユーティリティまたはBULK INSERTコマンドがあります。

そしてもちろん、このアプローチを処理するための独自のコードを実装することもできます。


1
一般に、インデックスを必要とするテーブルに多数の行を挿入する必要がある場合は、インデックスを削除し、データをロードしてからインデックスを再構築する方が高速です。Oracleは、/ * + APPEND * /ヒントを使用して、ダイレクトパスバルクロードオプションもサポートしています。
BobC 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.