mysql에서 업데이트와 삽입 쿼리를 단일 쿼리로 결합하는 것과 관련된 쿼리


9

사용자의 변경 기록을 추적하여 프로파일을 변경할 때마다 이전 데이터를 가져 와서 기록에 저장하고 새 데이터로 업데이트해야합니다.

나는 a select를 사용 하여 오래된 데이터, insertto history 및 마지막으로 update데이터를 변경할 수 있습니다.

잠금 등을 사용하는 것과 같은 저장 프로 시저, 트리거 등을 사용하지 않고 mysql의 단일 쿼리 에서이 모든 것을 가질 수 있습니까? 그렇다면 작은 샘플을 제공하십시오.


1
@savaranan :이 질문은 DBA와 개발자에게 트랜잭션을 사용하고 데이터베이스의 ACID 속성을 최대한 활용하도록 강력하게 상기시켜주기 때문에 +1에 해당합니다.
RolandoMySQLDBA 2016 년

2
@savaranan : 모든 의도와 목적을 위해 Jack은 그에 대한 유일한 해답을 제공했습니다. 실제로 Jack Douglas는 추가 단계를 수행하고 SELECT ... FOR UPDATE를 수행하여 MVCC 보호를 추가하기 위해 id = 10으로 모든 행에서 간헐적 잠금을 강제했습니다. 그의 대답은 Jack과 요점을 강조합니다. 업데이트와 INSERT는 단일 쿼리 일 수 없으며 쿼리가 제안하는 SQL 동작에 대한 단일 트랜잭션 일 수 없습니다.
RolandoMySQLDBA 1

답변:


13

같은 시간에 같은 프로필을 업데이트하려고 다른 사용자를 차단의 위험없이이 작업을 수행하려면 다음을 수행해야 행을 잠 에서 t1다음 사용, 최초의 트랜잭션을 (롤란가 귀하의 질문에 대한 코멘트에 지적으로) :

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

1

세 가지 진술을 모두 결합 할 수있는 방법이 없다고 생각합니다. 가장 가까운 것은 실제로 도움이되지 않으며 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
이것은 실제로 쿼리가 아닙니다. 이것은 실제로 트랜잭션으로 취급되어야하는 두 개의 쿼리입니다.
RolandoMySQLDBA 2016 년

@rolandomysqldba : 응용 프로그램 코드에서 DB 서버로 보낼 때 단일 쿼리로 잘 작동합니다.이 세트를 단일 쿼리로 취급합니다. 왜 그렇게 말합니까?. 당신은 이것을 강력한 이유들로 반증 할 수 있습니다 ..
Saravanan

2
@saravanan : InnoDB 또는 ACID 호환 RDBMS (Oracle, SQLServer, PostreSQL, Sybase 등)의 관점에서이 두 SQL 문을 하나의 쿼리로 호출하는 것은 불가능합니다. ACID 호환 데이터베이스는이를 두 개의 명령문으로 취급합니다. 기본적으로 InnoDB는 자동 커밋을 켭니다. 첫 번째 명령문 인 INSERT는 단일 트랜잭션으로 실행됩니다. MVCC (Multiversioning Concurrency Control) 데이터는 t2 테이블의 원래 데이터 사본을 행 단위로 유지하기 위해 생성됩니다. INSERT 실행 중에 MySQL이 충돌하면 InnoDB는 MVCC 데이터를 사용하여 t2를 원래 상태로 롤백합니다.
RolandoMySQLDBA 2016 년

1
@saravanan : INSERT가 성공적으로 작동했다고 가정하십시오. INSERT의 결과 데이터가 커미트되고 (자동 커미트 ON으로) MVCC 보호 테이블 t2가 삭제됩니다. UPDATE를 수행하면 t1 테이블에 대해 MVCC가 생성되고 UPDATE가 수행됩니다. UPDATE 도중 MySQL이 충돌하면 InnoDB는 t1의 MVCC 데이터를 사용하여 UPDATE를 롤백합니다. UPDATE가 하나의 행만 변경하더라도 백만 분의 1은 레코드가 t1에서 t2로 id 10 인 t2로 이동하고 t1에서 id 10을 id 11로 변경하지 않을 가능성이 있습니다. 이 고유 한 시나리오를 방지하려면 다음을 수행해야합니다.
RolandoMySQLDBA

@savaranan : 두 개의 SQL 문을 단일 트랜잭션으로 취급하십시오. 이를 수행하는 간단한 방법은 다음과 같습니다. BEGIN; t2에 삽입 * SELECT t1에서 ID = 10; 업데이트 t1 SET id = 11 어디서 id = 10; 범하다; 두 SQL 문을 단일 트랜잭션으로 취급하는 가장 강력한 이유는 INSERT 용으로 작성된 MVCC가 UPDATE 동안에도 계속 존재하기 때문입니다. 트랜잭션 (BEGIN; ... COMMIT; 블록) 내부의 UPDATE 중에 MySQL 충돌이 발생하면 MVCC는 모든 변경 사항을 일관된 상태로 롤백합니다. INSERT 및 UPDATE가 모두 완료되면 MVCC는 마지막 순간에 삭제됩니다.
RolandoMySQLDBA
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.