Primary Key 혹은 Unique Index가 생성되어 있는 테이블 데이터를 Insert 할 때, "Error Code: 1062. Duplicate entry '??' for key PRIMARY" 같은 에러를 자주 보게 된다. 테이블에 테이터를 Insert 하려는데 동일한 Key를 갖는 Row가 이미 테이블에 존재하는 경우 만나게 되는 에러다.
테이블에 데이터가 없으면 insert 하고 있으면 update 하는 동작이 필요한 경우가 있다. 이 때, select를 해보고 insert, update를 판단하는 동작으로 애플리케이션을 작성할 수도 있다. 최고의 시나리오는 다음과 같을 것이다.
하지만 select 이후 중복된 키의 존재 유무를 판단하고, insert 혹은 update를 수행하면 phantom read가 문제로 다가온다.
두 개의 세션이 동시에 select & insert 를 수행한다고 가정해보자. 두 세션 모두 select에서 특정 Key를 갖는 row가 없다고 판단할 수 있다. 이 후 두 세션 모두 테이블에 insert 하려고 시도할 수 있고, 둘 중 하나는 결국 똑같은 에러를 보게 된다.
이를 해소하기 위해 클라이언트 쪽에서 Lock 등을 이용한 동기화를 시도해볼 수 있다. 하지만 클라이언트가 서로 다른 서버에서 수행되는 등의 경우 처리가 복잡해진다. 나는 그냥 있으면 update하고 없으면 insert 하고 싶을 뿐인데 말이다.
클라이언트가 가장 간단하게 사용할 수 있는 방법은 서버에서 제공해주는 문법을 사용하는 것이다. 오라클(Oracle)에서는 Merge Into라는 구문을 제공해서 이런 문제를 해소할 수 있게 해준다. 오라클의 Merge Into 구문은 다음과 같다.
1 2 3 4 5 6 7 | MERGE INTO [table_name alias] USING [table | view | subquery] ON [joint condition] WHEN MATCHED THEN UPDATE SET column1 = value1, column2 = value2 ... WHEN NOT MATCHED THEN INSERT (column1, column2, ... ) VALUES (values1, values2, ...); | cs |
merge into 구문은 오라클 9i부터 추가된 기능이다. 10g부터는 UPDATE 대신 DELETE도 사용할 수 있게 되었지만 이 포스트에서는 다루지 않겠다.
MySQL에서의 Merge into 구문
오라클에서 지원하는 Merge into 구문은 MySQL에서는 지원되지 않는다. 대신 비슷한 기능을 하는 몇 가지 문법이 있다.
1) INSERT IGNORE
2) REPLACE INTO
3) INSERT ... ON DUPLICATE UPDATE
각각에 대해서 예제와 함께 알아보겠다. 먼저 테스트를 위해 테이블을 하나 만들어 보겠다.
1 2 3 4 5 6 7 | CREATE TABLE `t1` ( `id` int(11) NOT NULL AUTO_INCREMENT, `entry_name` varchar(16) DEFAULT NULL, `size` int(11) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `entry_name_UNIQUE` (`entry_name`) ); | cs |
이 테이블에 테스트를 위한 ROW를 하나 INSERT 해보자.
1 | INSERT INTO `T1` VALUES (NULL, 'ENTRY1', 20); | cs |
ROW 하나가 테이블에 INSERT 되어 있다.
1 2 3 4 5 6 7 8 | mysql> select * from t1; +----+------------+------+ | id | entry_name | size | +----+------------+------+ | 1 | ENTRY1 | 20 | +----+------------+------+ 1 row in set (0.00 sec) |
1) INSERT IGNORE
1 | insert into `t1` values (null, 'ENTRY1', 21); | cs |
1 2 | mysql> insert into `t1` values (null, 'ENTRY2', 21); Query OK, 1 row affected (0.01 sec) | cs |
1 2 3 4 5 6 7 8 | mysql> select * from t1; +----+------------+------+ | id | entry_name | size | +----+------------+------+ | 1 | ENTRY1 | 20 | | 4 | ENTRY2 | 21 | +----+------------+------+ 2 rows in set (0.00 sec) | cs |
2) REPLACE INTO
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | mysql> select * from t1; +----+------------+------+ | id | entry_name | size | +----+------------+------+ | 1 | ENTRY1 | 20 | | 4 | ENTRY2 | 21 | +----+------------+------+ 2 rows in set (0.00 sec) mysql> replace into t1 values (NULL, 'ENTRY3', 22); Query OK, 1 row affected (0.01 sec) mysql> select * from t1; +----+------------+------+ | id | entry_name | size | +----+------------+------+ | 1 | ENTRY1 | 20 | | 4 | ENTRY2 | 21 | | 5 | ENTRY3 | 22 | +----+------------+------+ 3 rows in set (0.00 sec) | cs |
1 2 3 4 5 6 7 8 9 10 11 12 | mysql> replace into t1 values (NULL, 'ENTRY1', 23); Query OK, 2 rows affected (0.01 sec) mysql> select * from t1; +----+------------+------+ | id | entry_name | size | +----+------------+------+ | 4 | ENTRY2 | 21 | | 5 | ENTRY3 | 22 | | 6 | ENTRY1 | 23 | +----+------------+------+ 3 rows in set (0.00 sec) | cs |
3) INSERT ... ON DUPLICATE KEY UPDATE
1 2 3 4 5 6 7 8 9 10 11 12 13 | mysql> insert into t1 values (NULL, 'ENTRY4', 24) ON DUPLICATE KEY UPDATE size = 24; Query OK, 1 row affected (0.01 sec) mysql> select * from t1; +----+------------+------+ | id | entry_name | size | +----+------------+------+ | 4 | ENTRY2 | 21 | | 5 | ENTRY3 | 22 | | 6 | ENTRY1 | 23 | | 7 | ENTRY4 | 24 | +----+------------+------+ 4 rows in set (0.00 sec) | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 | mysql> insert into t1 values (NULL, 'ENTRY3', 25) ON DUPLICATE KEY UPDATE size = 25; Query OK, 2 rows affected (0.01 sec) mysql> select * from t1; +----+------------+------+ | id | entry_name | size | +----+------------+------+ | 4 | ENTRY2 | 21 | | 5 | ENTRY3 | 25 | | 6 | ENTRY1 | 23 | | 7 | ENTRY4 | 24 | +----+------------+------+ 4 rows in set (0.00 sec) | cs |
1 2 | $ mysql --version mysql Ver 14.14 Distrib 5.7.22, for osx10.13 (x86_64) using EditLine wrapper | cs |
댓글