MySQL の AUTO_INCREMENT フィールドと InnoDB を使用してトランザクションをサポートしています。トランザクションをロールバックすると、AUTO_INCREMENT フィールドがロールバックされないことに気付きました。このように設計されていることがわかりましたが、これに対する回避策はありますか?
11 に答える
そのようには機能しません。検討:
- プログラム 1 では、トランザクションを開き、autoinc プライマリ キーを持つテーブル FOO に挿入します (任意に、キー値として 557 を取得すると言います)。
- プログラム 2 が開始され、トランザクションが開かれ、558 を取得してテーブル FOO に挿入されます。
- FOO への外部キーである列を持つテーブル BAR に 2 つの挿入をプログラムします。そのため、558 は FOO と BAR の両方に配置されています。
- プログラム 2 がコミットされます。
- プログラム 3 が開始され、テーブル FOO からレポートが生成されます。558 レコードが印刷されます。
- その後、プログラム 1 はロールバックします。
データベースは 557 値をどのように再利用しますか? FOOに入り、557を超える他のすべての主キーを減らしますか? BAR はどのように修正されますか? レポート プログラム 3 の出力に印刷された 558 をどのように消去しますか?
同じ理由で、Oracle のシーケンス番号もトランザクションから独立しています。
この問題を一定時間で解決できれば、データベース分野で大儲けできると確信しています。
ここで、自動インクリメント フィールドにギャップがないようにする必要がある場合 (監査目的など)。その後、トランザクションをロールバックできません。代わりに、レコードにステータス フラグを付ける必要があります。最初の挿入では、レコードのステータスは「未完了」です。その後、トランザクションを開始し、作業を行い、ステータスを「競合」(または必要なもの) に更新します。その後、コミットすると、レコードがライブになります。トランザクションがロールバックした場合、不完全なレコードはまだ監査用に残っています。これは他の多くの頭痛の種になりますが、監査証跡を処理する 1 つの方法です。
非常に重要なことを指摘させてください。
自動生成されたキーの数値機能に依存するべきではありません。
つまり、等しい (=) か等しくない (<>) かを比較する以外に、何もすべきではありません。関係演算子 (<、>)、インデックスによる並べ替えなどはありません。「追加日」で並べ替える必要がある場合は、「追加日」列を用意してください。
それらをリンゴとオレンジとして扱います: リンゴがオレンジと同じかどうかを尋ねることは理にかなっていますか? はい。リンゴがオレンジよりも大きいかどうかを尋ねるのは理にかなっていますか? いいえ(実際にはそうですが、私の言いたいことはわかります。)
このルールを守れば、自動生成されたインデックスの連続性にギャップがあっても問題は発生しません。
注文が連続している必要がある請求書のテーブルでロールバックするためにクライアントにIDが必要でした
MySQL での私の解決策は、AUTO-INCREMENT を削除してテーブルから最新の Id を取得し、1 つ (+1) を追加してから手動で挿入することでした。
テーブルの名前が「TableA」で、自動インクリメント列が「Id」の場合
INSERT INTO TableA (Id, Col2, Col3, Col4, ...)
VALUES (
(SELECT Id FROM TableA t ORDER BY t.Id DESC LIMIT 1)+1,
Col2_Val, Col3_Val, Col4_Val, ...)
ロールバックされていることを気にするのはなぜですか?AUTO_INCREMENT キー フィールドは意味を持たないと想定されているため、どの値が使用されているかを実際に気にする必要はありません。
保存しようとしている情報がある場合は、おそらく別の非キー列が必要です。
私はそれを行う方法を知りません。MySQL ドキュメントによると、これは予期された動作であり、すべてのinnodb_autoinc_lock_modeロック モードで発生します。具体的なテキストは次のとおりです。
すべてのロック モード (0、1、および 2) で、自動インクリメント値を生成したトランザクションがロールバックすると、それらの自動インクリメント値は「失われます」。自動インクリメント列の値が生成されると、「INSERT のような」ステートメントが完了したかどうか、および含まれているトランザクションがロールバックされたかどうかにかかわらず、ロールバックできません。このような失われた値は再利用されません。したがって、表の AUTO_INCREMENT 列に格納されている値にギャップが生じる場合があります。
ロールバックまたは削除後にを設定auto_increment
すると、次の挿入時に、MySQL はそれが既に使用されていることを認識し、代わりに値を取得してそれに 1 を追加します。1
1
MAX()
これにより、最後の値を持つ行が削除された (または挿入がロールバックされた) 場合に、その行が再利用されることが保証されます。
auto_increment を 1 に設定するには、次のようにします。
ALTER TABLE tbl auto_increment = 1
これはコストがかかる可能性があるため、単に次の番号に進むほど効率的ではありませんMAX()
が、削除/ロールバックの頻度が低く、最高値を再利用することに執着している場合、これは現実的なアプローチです。
これは、途中で削除されたレコードからのギャップ、または auto_increment を 1 に戻す前に別の挿入が発生する場合を防ぐものではないことに注意してください。
INSERT INTO prueba(id)
VALUES (
(SELECT IFNULL( MAX( id ) , 0 )+1 FROM prueba target))
テーブルに値またはゼロ行が含まれていない場合
SELECTでエラーmysqltypeupdateFROMのターゲットを追加します
この特定のジレンマ (私も抱えていた) に対する具体的な答えは次のとおりです。
1) さまざまなドキュメント (請求書、領収書、RMA など) のさまざまなカウンターを保持するテーブルを作成します。ドキュメントごとにレコードを挿入し、初期カウンターを 0 に追加します。
2) 新しいドキュメントを作成する前に、次のことを行います (たとえば、請求書の場合)。
UPDATE document_counters SET counter = LAST_INSERT_ID(counter + 1) where type = 'invoice'
3) 次のように、更新した最後の値を取得します。
SELECT LAST_INSERT_ID()
または、PHP(または何でも) mysql_insert_id() 関数を使用して同じものを取得します
4) DB から取得したばかりのプライマリ ID と共に新しいレコードを挿入します。これにより、現在の自動インクリメント インデックスが上書きされ、レコード間に ID ギャップがないことが確認されます。
もちろん、このすべてをトランザクション内にラップする必要があります。この方法の優れた点は、トランザクションをロールバックすると、ステップ 2 の UPDATE ステートメントがロールバックされ、カウンターが変更されなくなることです。他の同時トランザクションは、最初のトランザクションがコミットまたはロールバックされるまでブロックされるため、他のすべてのトランザクションが最初に終了するまで、古いカウンターにも新しいカウンターにもアクセスできません。
ID をギャップなしで番号順に割り当てる必要がある場合は、自動インクリメント列を使用できません。標準の整数列を定義し、挿入シーケンスの次の番号を計算してトランザクション内にレコードを挿入するストアド プロシージャを使用する必要があります。挿入が失敗した場合、次にプロシージャが呼び出されたときに、次の ID が再計算されます。
そうは言っても、ID がギャップのない特定の順序になっていることに依存するのは悪い考えです。順序を保持する必要がある場合は、おそらく挿入時に (場合によっては更新時に) 行にタイムスタンプを付ける必要があります。