インデックスとトランザクションの両方が関与する挿入のイベントのシーケンスを理解しようとしています。
たとえば、Oracleのドキュメントには次のように記載されています。
データをロードする前に1つ以上のインデックスを作成[または保持]する場合、データベースは各行が挿入されるたびにすべてのインデックスを更新する必要があります。
しかし、トランザクションを作成し、5つの行を挿入してからコミットするとどうなりますか?インデックスは挿入ごとに更新されますか、それともコミットポイントで更新されますか?
更新されたインデックスはこれらのレコードがコミットされるまで役に立たない可能性があるため、ロジックはコミットポイントでのみ更新されることを教えてくれました。しかし、それは本当ですか?
もしそうなら、挿入する行が1mある場合、最高のパフォーマンスを得るには、100kレコードの10トランザクションではなく、すべての行の1つの大きなコミットを実行する必要がありますか?もちろん、これは、行999,999が失敗した場合にロールバックが大きくなるリスクがあることを理解しています。
私の専門用語が少し出ていればお詫びします。私は貿易ではDBAではありません。私は特定のデータベースにはあまり関心がありません。データベース全般についてですが、私が最もよく使用しているのはOracleとPostgresです。私はこのトピックについて検索しましたが、決定的な答えは本当に見つかりません。
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';
次に、トランザクションを開始して、テーブルのほとんどすべての行のVAL
を更新します。
BEGIN TRANSACTION
UPDATE X_TABLE_IX
SET VAL = 'A'
WHERE ID <> 1;
これは、そのためのクエリプランの一部です。
赤で囲まれた部分は、非クラスター化インデックスの更新です。青色で囲まれているのは、本質的にテーブルのデータであるクラスター化インデックスの更新です。トランザクションはコミットされていませんが、クエリの実行の一部でデータとインデックスが更新されていることがわかります。関係するデータのサイズとその他の要因によっては、これが計画に常に表示されるとは限らないことに注意してください。
トランザクションがまだコミットされていない状態で、上からSELECT
クエリをもう一度見てみましょう。
SELECT *
FROM X_TABLE_IX
WHERE VAL = 'A';
クエリオプティマイザーは引き続きインデックスを使用でき、今回は999999行が返されると推定します。クエリを実行すると、予期した結果が返されます。
これは簡単なデモでしたが、うまくいけば、少しはうまくいきました。
余談ですが、インデックスがすぐに更新されないと主張される可能性のあるいくつかのケースを知っています。これはパフォーマンス上の理由で行われ、エンドユーザーは一貫性のないデータを見ることができないはずです。たとえば、SQL Serverのインデックスに削除が完全に適用されない場合があります。バックグラウンドプロセスが実行され、最終的にデータがクリーンアップされます。興味があれば ghost records について読むことができます。
私の経験では、1,000,000行の挿入は実際にはより多くのリソースを必要とし、バッチ挿入を使用した場合よりも完了するまでに時間がかかります。これは、例として、10,000行の100挿入に実装できます。
これにより、挿入されるバッチのオーバーヘッドが削減され、バッチが失敗した場合のロールバックは小さくなります。
いずれの場合でも、SQL Serverには、バッチ挿入を行うために使用できるbcpユーティリティまたはBULK INSERTコマンドがあります。
そしてもちろん、このアプローチを処理するための独自のコードを実装することもできます。