10

私は多かれ少なかれ次のように見える複雑な更新クエリを作成しています:

update table join
    (select y, min(x) as MinX 
     from table
     group by y) as t1
    using (y)
set x = x - MinX

これは、変数xも処理するサブクエリに基づいて変数が更新されるxことを意味しますが、これは実行中の update コマンドによって既に変更されていませんxか? これは問題ではありませんか?つまり、通常のプログラミングでは、通常、これを明示的に処理する必要があります。つまり、新しい値を古い値から別の場所に保存し、ジョブが完了したら、古い値を新しい値に置き換えます...しかし、SQLデータベースはこれをどのように行いますか?

私は単一の観察や実験には興味がありません。この場合、定義された動作が何であるかを示すドキュメントまたはSQL標準からのスニペットが必要です。私はMySQLを使用していますが、他のPostgresQL、Oracleなど、特にSQL標準全般に対しても有効な回答をいただければ幸いです。ありがとう!

4

4 に答える 4

5

**編集済み**

ターゲット表からの選択

13.2.9.8から。FROM句のサブクエリ:

FROM 句のサブクエリは、スカラー、列、行、またはテーブルを返すことができます。JOIN 操作の ON 句内で使用されない限り、FR​​OM 句のサブクエリを相関サブクエリにすることはできません。

はい、上記のクエリを実行できます。

問題

ここには実際には 2 つの問題があります。並行性があります。つまり、他の誰かが足元からデータを変更しないようにします。これはロックで処理されます。新しい値と古い値の実際の変更は、派生テーブルで処理されます。

ロック

上記のクエリの場合、InnoDB を使用すると、MySQL は最初に SELECT を実行し、テーブル内の各行に対して個別に読み取り (共有) ロックを取得します。SELECT ステートメントに WHERE 句がある場合、選択したレコードのみがロックされ、範囲によってギャップもロックされます。

読み取りロックは、他のクエリが書き込みロックを取得するのを防ぎます。そのため、読み取りがロックされている間、他の場所からレコードを更新することはできません。

次に、MySQL は、テーブル内の各レコードに対して個別に書き込み (排他的) ロックを取得します。UPDATE ステートメントに WHERE 句がある場合は、特定のレコードのみが書き込みロックされ、WHERE 句で範囲が選択されている場合は範囲​​がロックされます。

以前の SELECT からの読み取りロックを持っていたレコードは、自動的に書き込みロックにエスカレートされます。

書き込みロックは、他のクエリが読み取りロックまたは書き込みロックを取得するのを防ぎます。

Innotopをロック モードで実行し、トランザクションを開始し、クエリを実行することでこれを確認できます (ただし、コミットしないでください)。Innotop でロックが表示されます。また、Innotop を使用せずに詳細を表示することもできますSHOW ENGINE INNODB STATUS

デッドロック

2 つのインスタンスが同時に実行された場合、クエリはデッドロックに対して脆弱です。クエリ A が読み取りロックを取得し、次にクエリ B が読み取りロックを取得した場合、クエリ A は書き込みロックを取得する前に、クエリ B の読み取りロックが解放されるまで待機する必要があります。ただし、クエリ B は終了するまで読み取りロックを解放せず、書き込みロックを取得できない限り終了しません。クエリ A とクエリ B が膠着状態にあるため、デッドロックが発生しています。

したがって、大量のレコード ロック (メモリを使用し、パフォーマンスに影響を与える) を回避し、デッドロックを回避するために、明示的なテーブル ロックを実行することをお勧めします。

別のアプローチは、内部の SELECT で SELECT ... FOR UPDATE を使用することです。これは、読み取りから開始してそれらをエスカレートするのではなく、すべての行の書き込みロックから始まります。

派生テーブル

内部 SELECT の場合、MySQL は派生一時テーブルを作成します。派生テーブルは、(明示的に作成してインデックスを追加できる一時テーブルとは対照的に) MySQL によって自動的に作成される一時テーブルに存在するデータの実際の非インデックス コピーです。

MySQL は派生テーブルを使用するため、質問で参照するのは一時的な古い値です。言い換えれば、ここには魔法はありません。MySQL は、一時的な値を使用して、他の場所で行うのと同じように行います。

UPDATE ステートメントに対して EXPLAIN を実行すると、派生テーブルを確認できます (MySQL 5.6 以降でサポートされています)。

于 2012-04-10T16:47:29.007 に答える
2

適切な RDBMS は を使用しますstatement level read consistency。これにより、ステートメントは、ステートメントが開始された時点のデータを認識 (選択) します。したがって、あなたが恐れているシナリオは発生しません。

よろしく、
ロブ。

于 2012-04-13T14:43:06.403 に答える
1

Oracle は 11.2ドキュメントにこれを持っています

すべてのクエリに対して一貫性のある結果セットが提供され、データの一貫性が保証され、ユーザーによる操作は必要ありません。UPDATE ステートメントの WHERE 句によって暗示されたクエリなどの暗黙的なクエリでは、結果の一貫したセットが保証されます。ただし、暗黙的なクエリの各ステートメントは、DML ステートメント自体によって行われた変更を認識しませんが、変更が行われる前に存在していたデータを認識します。

于 2012-04-13T13:29:48.643 に答える
0

独自のデータに基づいてテーブルを更新することはできませんが、MySQL構文を調整して次の方法で更新できるようにする必要があります。

update Table1, 
       (select T2.y, MIN( T2.x ) as MinX from Table1 T2 group by T2.y ) PreQuery
  set Table1.x = Table1.x - PreQuery.MinX
  where Table1.y = PreQuery.y

JOINとコンマリストバージョンを使用して構文が異なるルートになるかどうかはわかりませんが、完全な事前クエリでは、最初に結果を1回適用し、(WHEREを介して)結合して実際に実行する必要があります。アップデート。

于 2012-04-10T18:02:41.680 に答える