on duplicate mysql_mysql 避免重复写入数据的三种方式 和insert ...on duplicate updt... 死锁...
mysql在存在主鍵沖突或者唯一鍵沖突的情況下,根據(jù)插入策略不同,一般有以下三種避免方法。
1、insert ignore
2、replace into
3、insert on duplicate key update
注意,除非表有一個(gè)PRIMARY KEY或UNIQUE索引,否則,使用以上三個(gè)語(yǔ)句沒有意義,與使用單純的INSERT INTO相同。
一、insert ignore
insert ignore會(huì)忽略數(shù)據(jù)庫(kù)中已經(jīng)存在的數(shù)據(jù)(根據(jù)主鍵或者唯一索引判斷),如果數(shù)據(jù)庫(kù)沒有數(shù)據(jù),就插入新的數(shù)據(jù),如果有數(shù)據(jù)的話就跳過這條數(shù)據(jù).
Case:
表結(jié)構(gòu)如下:
復(fù)制代碼
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)
復(fù)制代碼
測(cè)試插入唯一鍵沖突的數(shù)據(jù)
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)這條,同時(shí)有一條warning提示有重復(fù)的值。
復(fù)制代碼
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)
復(fù)制代碼
重新查詢表結(jié)構(gòu),發(fā)現(xiàn)雖然只增加了一條記錄,但是AUTO_INCREMENT還是增加了2個(gè)(18變成20)
復(fù)制代碼
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)
復(fù)制代碼
二、replace into
replace into 首先嘗試插入數(shù)據(jù)到表中。 如果發(fā)現(xiàn)表中已經(jīng)有此行數(shù)據(jù)(根據(jù)主鍵或者唯一索引判斷)則先刪除此行數(shù)據(jù),然后插入新的數(shù)據(jù),否則,直接插入新數(shù)據(jù)。
使用replace into,你必須具有delete和insert權(quán)限
Case:
復(fù)制代碼
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)
復(fù)制代碼
插入一條與記錄id=3存在唯一鍵(列c1)沖突的數(shù)據(jù)
復(fù)制代碼
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)
復(fù)制代碼
可以看到原有id=3,c1=3的記錄不見了,新增了一條id=4,c1=3的記錄.
replace into語(yǔ)句執(zhí)行完會(huì)返回一個(gè)數(shù),來(lái)指示受影響的行的數(shù)目。該數(shù)是被刪除和被插入的行數(shù)的和,上面的例子中2 rows affected .
三、insert on duplicate key update
如果在insert into 語(yǔ)句末尾指定了on duplicate key update,并且插入行后會(huì)導(dǎo)致在一個(gè)UNIQUE索引或PRIMARY KEY中出現(xiàn)重復(fù)值,則在出現(xiàn)重復(fù)值的行執(zhí)行UPDATE;如果不會(huì)導(dǎo)致重復(fù)的問題,則插入新行,跟普通的insert into一樣。
使用insert into,你必須具有insert和update權(quán)限
如果有新記錄被插入,則受影響行的值顯示1;如果原有的記錄被更新,則受影響行的值顯示2;如果記錄被更新前后值是一樣的,則受影響行數(shù)的值顯示0
Case:
復(fù)制代碼
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)
復(fù)制代碼
插入一條與記錄id=3存在唯一鍵(列c1)沖突的數(shù)據(jù)
復(fù)制代碼
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)
復(fù)制代碼
可以看到,id=3的記錄發(fā)生了改變,c1=原有的c1+3,其他列沒有改變。
結(jié)論:
這三種方法都能避免主鍵或者唯一索引重復(fù)導(dǎo)致的插入失敗問題。
insert ignore能忽略重復(fù)數(shù)據(jù),只插入不重復(fù)的數(shù)據(jù)。
replace into和insert … on duplicate key update,都是替換原有的重復(fù)數(shù)據(jù),區(qū)別在于replace into是刪除原有的行后,在插入新行,如有自增id,這個(gè)會(huì)造成自增id的改變;insert … on duplicate key update在遇到重復(fù)行時(shí),會(huì)直接更新原有的行,具體更新哪些字段怎么更新,取決于update后的語(yǔ)句。
分割線分割線分割線分割線分割線分割線分割線分割線
我們?cè)趯?shí)際業(yè)務(wù)場(chǎng)景中,經(jīng)常會(huì)有一個(gè)這樣的需求,插入某條記錄,如果已經(jīng)存在了則更新它如果更新日期或者某些列上的累加操作等,我們肯定會(huì)想到使用INSERT … ON DUPLICATE KEY UPDATE語(yǔ)句,一條語(yǔ)句就搞定了查詢是否存在和插入或者更新這幾個(gè)步驟,但是使用這條語(yǔ)句在msyql的innodb5.0以上版本有很多的陷阱,即有可能導(dǎo)致death lock死鎖也有可能導(dǎo)致主從模式下的replication產(chǎn)生數(shù)據(jù)不一致。
正文
正如前言說(shuō)的那樣,在實(shí)際業(yè)務(wù)中,曾經(jīng)有過一個(gè)需求就是插入一條業(yè)務(wù)數(shù)據(jù),如果不存在則新增,存在則累加更新某一個(gè)字段的值,于是乎就想到了使用insert… on duplicate key update這個(gè)語(yǔ)句,但是有一天去測(cè)試環(huán)境查看錯(cuò)誤日志時(shí),卻發(fā)現(xiàn)了在多個(gè)事務(wù)并發(fā)執(zhí)行同一條insert…on duplicate key update 語(yǔ)句時(shí),也就是insert的內(nèi)容相同時(shí),發(fā)生 了死鎖。
對(duì)于insert…on duplicate key update這個(gè)語(yǔ)句會(huì)引發(fā)dealth lock問題,官方文檔也沒有相關(guān)描述,只是進(jìn)行如下描述:
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)
也就是如果一個(gè)表定義有多個(gè)唯一鍵或者主鍵時(shí),是不安全的,這又引發(fā)了以一個(gè)問題,見https://bugs.mysql.com/bug.php?id=58637
也就是
當(dāng)mysql執(zhí)行INSERT ON DUPLICATE KEY的 INSERT時(shí),存儲(chǔ)引擎會(huì)檢查插入的行是否會(huì)產(chǎn)生重復(fù)鍵錯(cuò)誤。如果是的話,它會(huì)將現(xiàn)有的
行返回給mysql,mysql會(huì)更新它并將其發(fā)送回存儲(chǔ)引擎。當(dāng)表具有多個(gè)唯一或主鍵時(shí),此語(yǔ)句對(duì)存儲(chǔ)引擎檢查密鑰的順序非常敏感。根據(jù)這個(gè)順序,
存儲(chǔ)引擎可以確定不同的行數(shù)據(jù)給到mysql,因此mysql可以更新不同的行。存儲(chǔ)引擎檢查key的順序不是確定性的。例如,InnoDB按照索引添加到
表的順序檢查鍵。首先檢查第一個(gè)添加的索引。
所以,如果主站和從站按不同的順序添加索引,那么如果主從復(fù)制是基于語(yǔ)句的復(fù)制,那么可能最后同一個(gè)語(yǔ)句在master上執(zhí)行和slaver上執(zhí)行的
結(jié)果不一致.
回到死鎖的問題
insert … on duplicate key 在執(zhí)行時(shí),innodb引擎會(huì)先判斷插入的行是否產(chǎn)生重復(fù)key錯(cuò)誤,如果存在,在對(duì)該現(xiàn)有的行加上S(共享鎖)鎖,如果返回該行數(shù)據(jù)給mysql,然后mysql執(zhí)行完duplicate后的update操作,然后對(duì)該記錄加上X(排他鎖),最后進(jìn)行update寫入。
如果有兩個(gè)事務(wù)并發(fā)的執(zhí)行同樣的語(yǔ)句,那么就會(huì)產(chǎn)生death lock,如:
解決辦法:
1、盡量對(duì)存在多個(gè)唯一鍵的table使用該語(yǔ)句
2、在有可能有并發(fā)事務(wù)執(zhí)行的insert 的內(nèi)容一樣情況下不使用該語(yǔ)句
總結(jié)
以上是生活随笔為你收集整理的on duplicate mysql_mysql 避免重复写入数据的三种方式 和insert ...on duplicate updt... 死锁...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Xxy 的车厢调度
- 下一篇: linux cmake编译源码,linu