mysqlで更新と挿入クエリを1つのクエリに結合することに関するクエリ


9

ユーザーの変更履歴を追跡したいので、ユーザーがプロファイルを変更するたびに、古いデータを取得して履歴に保存し、新しいデータで更新する必要があります。

a selectを使用して古いデータを取得し、inserttoを履歴に、最後にを使用してデータupdateを変更できます。

ロックなどを使用するようなストアドプロシージャ、トリガーなどを使用せずに、これらすべてをmysqlの単一のクエリで使用できます。


1
@savaranan:この質問は、トランザクションを使用してデータベースのACIDプロパティを最大限に活用することをDBAおよび開発者に強く思い出させるため、+ 1に値します。
RolandoMySQLDBA

2
@savaranan:あらゆる意図と目的のために、ジャックはそこにあるもっともらしい答えだけを提供しました。実際、Jack Douglasは追加のステップを実行し、SELECT ... FOR UPDATEを実行してMVCC保護を追加するために、id = 10のすべての行に断続的なロックを強制しました。彼の答えはジャックと私がずっと言ってきたポイントをさらに強調します:UPDATEとINSERTは単一のクエリになることはできず、決して単一のクエリになることはできません。
RolandoMySQLDBA 2011

回答:


13

同じプロファイルを同時に更新しようとする別のユーザーをブロックするリスクなしにこれを行うには、最初にロックしてからトランザクションをt1使用する必要があります(Rolandoが質問へのコメントで指摘しているように)。

start transaction;
select id from t1 where id=10 for update;
insert into t2 select * from t1 where id=10;
update t1 set id = 11 where id=10;
commit;

これは、id = 10のすべての行をさらにロックする点で優れています。+2である必要があります。私が与えることができるのは+1です!!!
RolandoMySQLDBA

1

3つのステートメントすべてを組み合わせる方法があるとは思いません。これに最も近いものは本当にあなたを助けません、そしてそれはSET SELECTです。あなたの最善の策はトリガーです。以下は、このような監査証跡(PHPで作成されたもの)を維持するためによく使用するトリガーのサンプルです。

$trigger = "-- audit trigger --\nDELIMITER $ \n".
    "DROP TRIGGER IF EXISTS `{$prefix}_Audit_Trigger`$\n".
    "CREATE TRIGGER `{$prefix}_Audit_Trigger` AFTER UPDATE ON `$this->_table_name` FOR EACH ROW BEGIN\n";

foreach ($field_defs as $field_name => $field) {
    if ($field_name != $id_name) {
       $trigger .= "IF (NOT OLD.$field_name <=> NEW.$field_name) THEN \n".'INSERT INTO AUDIT_LOG ('.
                    'Table_Name, Row_ID, Field_Name, Old_Value, New_Value, modified_by, DB_User) VALUES'.
                    "\n ('$this->_table_name',OLD.$this->_id_name,'$field_name',OLD.$field_name,NEW.$field_name,".
                    "NEW.modified_by, USER()); END IF;\n";
    }
}
$trigger .= 'END$'."\n".'DELIMITER ;';

-3

このクエリはSQLおよびMySQLサーバーで機能することがわかりました INSERT INTO t2 SELECT * FROM t1 WHERE id=10; UPDATE t1 SET id=11 WHERE id=10;

これが他の人にも将来的に役立つことを願っています。


4
これは実際にはクエリではありません。これは実際には2つのクエリであり、トランザクションとして扱う必要があります。
RolandoMySQLDBA 2011年

@rolandomysqldba:アプリケーションコードからdbサーバーに送信すると、これは単一のクエリとして正常に機能し、このセットを単一のクエリとして扱います。なぜそう言うのですか。あなたはこれを強い理由で反証することができますか?
サラバナン

2
@saravanan:InnoDBまたはACID準拠のRDBMS(Oracle、SQLServer、PostreSQL、Sybaseなど)の目には、これら2つのSQLステートメントを1つのクエリと呼ぶことは不可能です。ACID準拠のデータベースでは、これらを2つのステートメントとして扱います。デフォルトでは、InnoDBでは自動コミットがオンになっています。最初のステートメントINSERTは、単一のトランザクションとして実行されます。マルチバージョン同時実行制御(MVCC)データは、元のデータのコピーを行ごとにt2テーブルに保持するために生成されます。MySQLがINSERTの実行中にクラッシュした場合、InnoDBはMVCCデータを使用してt2を元の状態にロールバックします。
RolandoMySQLDBA '30年

1
@saravanan:INSERTが正常に機能したとします。INSERTの結果のデータはコミットされ(自動コミットがオンの場合)、MVCC保護テーブルt2は破棄されます。UPDATEを実行すると、t1テーブルに対してMVCCが生成され、UPDATEが実行されます。MySQLがUPDATE中にクラッシュした場合、InnoDBはt1のMVCCデータを使用してUPDATEをロールバックします。UPDATEが1行のみを変更する場合でも、レコードをt1からt2にid 10で移動し、t1でid 10をid 11に変更しないという可能性があります。この独特のシナリオを防ぐには、次のことを行う必要があります...
RolandoMySQLDBA

@savaranan:2つのSQLステートメントを1つのトランザクションとして扱います。これを行う簡単な方法は次のとおりです。INSERT INTO t2 SELECT * FROM t1 WHERE id = 10;に挿入します。UPDATE t1 SET id = 11 WHERE id = 10; コミット; 2つのSQLステートメントを単一のトランザクションとして扱う最大の理由は、INSERTのために作成されたMVCCがUPDATEの間も存在し続けるという事実です。トランザクション内のUPDATE(BEGIN; ... COMMIT;ブロック)中にMySQLのクラッシュが発生した場合、MVCCはすべての変更を一貫した状態にロールバックします。INSERTとUPDATEの両方が完了した場合、MVCCは最後に破棄されます。
RolandoMySQLDBA 2011年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.