mysql 避免重复写入数据的三种方式 和insert ...on duplicate updt... 死锁

时间:2019-02-18
本文章向大家介绍mysql 避免重复写入数据的三种方式 和insert ...on duplicate updt... 死锁,主要包括mysql 避免重复写入数据的三种方式 和insert ...on duplicate updt... 死锁使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

mysql在存在主键冲突或者唯一键冲突的情况下,根据插入策略不同,一般有以下三种避免方法。
1、insert ignore
2、replace into
3、insert on duplicate key update

注意,除非表有一个PRIMARY KEY或UNIQUE索引,否则,使用以上三个语句没有意义,与使用单纯的INSERT INTO相同。

一、insert ignore
insert ignore会忽略数据库中已经存在的数据(根据主键或者唯一索引判断),如果数据库没有数据,就插入新的数据,如果有数据的话就跳过这条数据.

Case:
表结构如下:

复制代码
root:test> show create table t3\G
*************************** 1. row ***************************
Table: t3
Create Table: CREATE TABLE t3 (
id int(11) NOT NULL AUTO_INCREMENT,
c1 int(11) DEFAULT NULL,
c2 varchar(20) DEFAULT NULL,
c3 int(11) DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY uidx_c1 (c1)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

root:test> select * from t3;
±—±-----±-----±-----+
| id | c1 | c2 | c3 |
±—±-----±-----±-----+
| 1 | 1 | a | 1 |
| 2 | 2 | a | 1 |
| 8 | NULL | NULL | 1 |
| 14 | 4 | bb | NULL |
| 17 | 5 | cc | 4 |
±—±-----±-----±-----+
5 rows in set (0.00 sec)
复制代码
测试插入唯一键冲突的数据

root:test> insert ignore into t3 (c1,c2,c3) values(5,‘cc’,4),(6,‘dd’,5); Query OK, 1 row affected, 1 warning (0.01 sec)
Records: 2 Duplicates: 1 Warnings: 1
如下,可以看到只插入了(6,‘dd’,5)这条,同时有一条warning提示有重复的值。

复制代码
root:test> show warnings;
±--------±-----±--------------------------------------+
| Level | Code | Message |
±--------±-----±--------------------------------------+
| Warning | 1062 | Duplicate entry ‘5’ for key ‘uidx_c1’ |
±--------±-----±--------------------------------------+
1 row in set (0.00 sec)

root:test> select * from t3;
±—±-----±-----±-----+
| id | c1 | c2 | c3 |
±—±-----±-----±-----+
| 1 | 1 | a | 1 |
| 2 | 2 | a | 1 |
| 8 | NULL | NULL | 1 |
| 14 | 4 | bb | NULL |
| 17 | 5 | cc | 4 |
| 18 | 6 | dd | 5 |
±—±-----±-----±-----+
6 rows in set (0.00 sec)
复制代码
重新查询表结构,发现虽然只增加了一条记录,但是AUTO_INCREMENT还是增加了2个(18变成20)

复制代码
root:test> show create table t3\G
*************************** 1. row ***************************
Table: t3

 Create Table: CREATE TABLE `t3` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `c1` int(11) DEFAULT NULL,
  `c2` varchar(20) DEFAULT NULL,
  `c3` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uidx_c1` (`c1`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

复制代码
二、replace into
replace into 首先尝试插入数据到表中。 如果发现表中已经有此行数据(根据主键或者唯一索引判断)则先删除此行数据,然后插入新的数据,否则,直接插入新数据。
使用replace into,你必须具有delete和insert权限
Case:
复制代码
root:test> show create table t3\G
*************************** 1. row ***************************
Table: t3
Create Table: CREATE TABLE t3 (
id int(11) NOT NULL AUTO_INCREMENT,
c1 int(11) DEFAULT NULL,
c2 varchar(20) DEFAULT NULL,
c3 int(11) DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY uidx_c1 (c1)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

root:test> select * from t3;
±—±-----±-------±-----+
| id | c1 | c2 | c3 |
±—±-----±-------±-----+
| 1 | 1 | cc | 4 |
| 2 | 2 | dd | 5 |
| 3 | 3 | qwewqe | 3 |
±—±-----±-------±-----+
3 rows in set (0.00 sec)
复制代码
插入一条与记录id=3存在唯一键(列c1)冲突的数据

复制代码
root:test> replace into t3 (c1,c2,c3) values(3,‘new’,8);
Query OK, 2 rows affected (0.02 sec)

root:test> select * from t3;
±—±-----±-----±-----+
| id | c1 | c2 | c3 |
±—±-----±-----±-----+
| 1 | 1 | cc | 4 |
| 2 | 2 | dd | 5 |
| 4 | 3 | new | 8 |
±—±-----±-----±-----+
3 rows in set (0.00 sec)
复制代码
可以看到原有id=3,c1=3的记录不见了,新增了一条id=4,c1=3的记录.
replace into语句执行完会返回一个数,来指示受影响的行的数目。该数是被删除和被插入的行数的和,上面的例子中2 rows affected .

三、insert on duplicate key update
如果在insert into 语句末尾指定了on duplicate key update,并且插入行后会导致在一个UNIQUE索引或PRIMARY KEY中出现重复值,则在出现重复值的行执行UPDATE;如果不会导致重复的问题,则插入新行,跟普通的insert into一样。
使用insert into,你必须具有insert和update权限
如果有新记录被插入,则受影响行的值显示1;如果原有的记录被更新,则受影响行的值显示2;如果记录被更新前后值是一样的,则受影响行数的值显示0
Case:
复制代码
root:test> show create table t3\G
*************************** 1. row ***************************
Table: t3
Create Table: CREATE TABLE t3 (
id int(11) NOT NULL AUTO_INCREMENT,
c1 int(11) DEFAULT NULL,
c2 varchar(20) DEFAULT NULL,
c3 int(11) DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY uidx_c1 (c1)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

root:test> select * from t3;
±—±-----±-----±-----+
| id | c1 | c2 | c3 |
±—±-----±-----±-----+
| 1 | 1 | fds | 4 |
| 2 | 2 | ytu | 3 |
| 3 | 3 | czx | 5 |
±—±-----±-----±-----+
3 rows in set (0.00 sec)
复制代码
插入一条与记录id=3存在唯一键(列c1)冲突的数据

复制代码
root:test> insert into t3(c1,c2,c3) values (3,‘new’,5) on duplicate key update c1=c1+3;
Query OK, 2 rows affected (0.01 sec)

root:test> select * from t3;
±—±-----±-----±-----+
| id | c1 | c2 | c3 |
±—±-----±-----±-----+
| 1 | 1 | fds | 4 |
| 2 | 2 | ytu | 3 |
| 3 | 6 | czx | 5 |
±—±-----±-----±-----+
3 rows in set (0.00 sec)
复制代码
可以看到,id=3的记录发生了改变,c1=原有的c1+3,其他列没有改变。

结论:
这三种方法都能避免主键或者唯一索引重复导致的插入失败问题。
insert ignore能忽略重复数据,只插入不重复的数据。
replace into和insert … on duplicate key update,都是替换原有的重复数据,区别在于replace into是删除原有的行后,在插入新行,如有自增id,这个会造成自增id的改变;insert … on duplicate key update在遇到重复行时,会直接更新原有的行,具体更新哪些字段怎么更新,取决于update后的语句。


分割线分割线分割线分割线分割线分割线分割线分割线

我们在实际业务场景中,经常会有一个这样的需求,插入某条记录,如果已经存在了则更新它如果更新日期或者某些列上的累加操作等,我们肯定会想到使用INSERT … ON DUPLICATE KEY UPDATE语句,一条语句就搞定了查询是否存在和插入或者更新这几个步骤,但是使用这条语句在msyql的innodb5.0以上版本有很多的陷阱,即有可能导致death lock死锁也有可能导致主从模式下的replication产生数据不一致。

正文

正如前言说的那样,在实际业务中,曾经有过一个需求就是插入一条业务数据,如果不存在则新增,存在则累加更新某一个字段的值,于是乎就想到了使用insert… on duplicate key update这个语句,但是有一天去测试环境查看错误日志时,却发现了在多个事务并发执行同一条insert…on duplicate key update 语句时,也就是insert的内容相同时,发生 了死锁。

对于insert…on duplicate key update这个语句会引发dealth lock问题,官方文档也没有相关描述,只是进行如下描述:

An INSERT … ON DUPLICATE KEY UPDATE statement against a table having more than one unique or primary key is also marked as unsafe. (Bug #11765650, Bug #58637)

也就是如果一个表定义有多个唯一键或者主键时,是不安全的,这又引发了以一个问题,见https://bugs.mysql.com/bug.php?id=58637

也就是

当mysql执行INSERT ON DUPLICATE KEY的 INSERT时,存储引擎会检查插入的行是否会产生重复键错误。如果是的话,它会将现有的

行返回给mysql,mysql会更新它并将其发送回存储引擎。当表具有多个唯一或主键时,此语句对存储引擎检查密钥的顺序非常敏感。根据这个顺序,

存储引擎可以确定不同的行数据给到mysql,因此mysql可以更新不同的行。存储引擎检查key的顺序不是确定性的。例如,InnoDB按照索引添加到

表的顺序检查键。首先检查第一个添加的索引。

所以,如果主站和从站按不同的顺序添加索引,那么如果主从复制是基于语句的复制,那么可能最后同一个语句在master上执行和slaver上执行的

结果不一致.

回到死锁的问题

insert … on duplicate key 在执行时,innodb引擎会先判断插入的行是否产生重复key错误,如果存在,在对该现有的行加上S(共享锁)锁,如果返回该行数据给mysql,然后mysql执行完duplicate后的update操作,然后对该记录加上X(排他锁),最后进行update写入。

如果有两个事务并发的执行同样的语句,那么就会产生death lock,如:


解决办法:

1、尽量对存在多个唯一键的table使用该语句

2、在有可能有并发事务执行的insert 的内容一样情况下不使用该语句