From: http://blog.xupeng.me/2013/10/11/mysql-replace-into-trap/

MySQL 对 SQL 有很多扩展,有些用起来很方便,但有一些被误用之后会有性能问题,还会有一些意料之外的副作用,比如 REPLACE INTO。

比如有这样一张表:

1
2
3
4
5
6
7
8
CREATE TABLE `auto` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`k` int(10) unsigned NOT NULL,
`v` varchar(100) DEFAULT NULL,
`extra` varchar(200) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_k` (`k`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1

auto 表有一个自增的 id 字段作为主键,字段 k 有 UNIQUE KEY 做唯一性约束。写入几条记录之后会是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
xupeng@diggle7:3600(dba_m) [dba] mysql> INSERT INTO auto (k, v, extra) VALUES (1, '1', 'extra 1'), (2, '2', 'extra 2'), (3, '3', 'extra 3');
Query OK, 3 rows affected (0.01 sec)
Records: 3 Duplicates: 0 Warnings: 0 xupeng@diggle7:3600(dba_m) [dba] mysql> SHOW CREATE TABLE auto\G
*************************** 1. row ***************************
Table: auto
Create Table: CREATE TABLE `auto` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`k` int(10) unsigned NOT NULL,
`v` varchar(100) DEFAULT NULL,
`extra` varchar(200) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_k` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1
1 row in set (0.01 sec) xupeng@diggle7:3600(dba_m) [dba] mysql> SELECT * FROM auto;
+----+---+------+---------+
| id | k | v | extra |
+----+---+------+---------+
| 1 | 1 | 1 | extra 1 |
| 2 | 2 | 2 | extra 2 |
| 3 | 3 | 3 | extra 3 |
+----+---+------+---------+
3 rows in set (0.00 sec)

在 slave 节点上是和 master 一致的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
xupeng@diggle8:3600(dba_s) [dba] mysql> SELECT * FROM auto;
+----+---+------+---------+
| id | k | v | extra |
+----+---+------+---------+
| 1 | 1 | 1 | extra 1 |
| 2 | 2 | 2 | extra 2 |
| 3 | 3 | 3 | extra 3 |
+----+---+------+---------+
3 rows in set (0.00 sec) xupeng@diggle8:3600(dba_s) [dba] mysql> SHOW CREATE TABLE auto\G
*************************** 1. row ***************************
Table: auto
Create Table: CREATE TABLE `auto` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`k` int(10) unsigned NOT NULL,
`v` varchar(100) DEFAULT NULL,
`extra` varchar(200) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_k` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

可以看到,写入三条记录之后,auto 表的 AUTO_INCREMENT 增长为 4,也就是说下一条不手工为 id 指定值的记录,id 字段的值会是 4。

接下来使用 REPLACE INTO 来写入一条记录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
xupeng@diggle7:3600(dba_m) [dba] mysql> REPLACE INTO auto (k, v) VALUES (1, '1-1');
Query OK, 2 rows affected (0.01 sec) xupeng@diggle7:3600(dba_m) [dba] mysql> SELECT * FROM auto;
+----+---+------+---------+
| id | k | v | extra |
+----+---+------+---------+
| 2 | 2 | 2 | extra 2 |
| 3 | 3 | 3 | extra 3 |
| 4 | 1 | 1-1 | NULL |
+----+---+------+---------+
3 rows in set (0.00 sec) xupeng@diggle7:3600(dba_m) [dba] mysql> SHOW CREATE TABLE auto\G
*************************** 1. row ***************************
Table: auto
Create Table: CREATE TABLE `auto` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`k` int(10) unsigned NOT NULL,
`v` varchar(100) DEFAULT NULL,
`extra` varchar(200) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_k` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

可以看到 MySQL 说 “2 rows affected”,可是明明是只写一条记录,为什么呢?这是因为 MySQL 在执行 REPLACE INTO auto (k) VALUES (1) 时首先尝试 INSERT INTO auto (k) VALUES (1),但由于已经存在一条 k=1 的记录,发生了 duplicate key error,于是 MySQL 会先删除已有的那条 k=1 即 id=1 的记录,然后重新写入一条新的记录。

这时候 slave 上出现了诡异的问题:

1
2
3
4
5
6
7
8
9
10
11
xupeng@diggle8:3600(dba_s) [dba] mysql> SHOW CREATE TABLE auto\G
*************************** 1. row ***************************
Table: auto
Create Table: CREATE TABLE `auto` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`k` int(10) unsigned NOT NULL,
`v` varchar(100) DEFAULT NULL,
`extra` varchar(200) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_k` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1

可以知道,当前表内数据 id 字段的最大值是 4,AUTO_INCREMENT 应该为 5,但在 slave 上 AUTO_INCREMENT 却并未更新,这会有什么问题呢?把这个 slave 提升为 master 之后,由于 AUTO_INCREMENT 比实际的 next id 还要小,写入新记录时就会发生 duplicate key error,每次冲突之后 AUTO_INCREMENT += 1,直到增长为 max(id) + 1 之后才能恢复正常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
xupeng@diggle8:3600(dba_s) [dba] mysql> REPLACE INTO auto (k, v) VALUES (4, '4');
ERROR 1062 (23000): Duplicate entry '4' for key 'PRIMARY'
xupeng@diggle8:3600(dba_s) [dba] mysql> REPLACE INTO auto (k, v) VALUES (5, '5');
Query OK, 1 row affected (0.00 sec) xupeng@diggle8:3600(dba_s) [dba] mysql> SELECT * FROM auto;
+----+---+------+---------+
| id | k | v | extra |
+----+---+------+---------+
| 2 | 2 | 2 | extra 2 |
| 3 | 3 | 3 | extra 3 |
| 4 | 1 | 1-1 | NULL |
| 5 | 5 | 5 | NULL |
+----+---+------+---------+
4 rows in set (0.00 sec)

没有预料到 MySQL 在数据冲突时实际上是删掉了旧记录,再写入新记录,这是使用 REPLACE INTO 时最大的一个误区,拿之前的例子来说,执行完 REPLACE INTO auto (k, v) VALUES (1, ‘1-1’) 之后,由于新写入记录时并未给 extra 字段指定值,原记录 extra 字段的值就「丢失」了,而通常这并非是业务上所预期的,更常见的需求实际上是,当存在 k=1 的记录时,就把 v 字段的值更新为 ‘1-1’,其他未指定的字段则保持原状,而满足这一需求的 MySQL 方言是 INSERT INTO auto (k, v) VALUES (1, ‘1-1’) ON DUPLICATE KEY UPDATE v=VALUES(v);

鉴于此,很多使用 REPLACE INTO 的场景,实际上需要的是 INSERT INTO … ON DUPLICATE KEY UPDATE,在正确理解 REPLACE INTO 行为和副作用的前提下,谨慎使用 REPLACE INTO。

 

MySQL谨慎使用"replace into"的更多相关文章

  1. Mysql笔记之 -- replace()实现mysql 替换字符串

    mysql 替换函数replace()实现mysql 替换字符串 mysql 替换字符串的实现方法:  mysql中replace函数直接替换mysql数据库中某字段中的特定字符串,不再需要自己写函数 ...

  2. MySQL中的replace语句

    一.背景 当使用replace语句更新access_apps表时,原有的mark列.remark列的信息丢失. CREATE TABLE `access_apps` (   `base` varcha ...

  3. mysql 替换函数replace()实现mysql 替换字符串

    mysql 替换字符串的实现方法:mysql中replace函数直接替换mysql数据库中某字段中的特定字符串,不再需要自己写函数去替换,用起来非常的方便,mysql 替换函数replace()Upd ...

  4. mysql 替换函数replace()实现mysql替换指定字段中的字符串

    mysql 替换字符串的实现方法: mysql中replace函数直接替换mysql数据库中某字段中的特定字符串,不再需要自己写函数去替换,用起来非常的方便. mysql 替换函数replace() ...

  5. MySQL特殊语法---replace into

    MySQL中有这样的SQL语句 1. replace into tbl_name(col_name, ...) values(...) 2. replace into tbl_name(col_nam ...

  6. Mysql INSERT、REPLACE、UPDATE的区别

    用于操作数据库的SQL一般分为两种,一种是查询语句,也就是我们所说的SELECT语句,另外一种就是更新语句,也叫做数据操作语句.言外之意,就是对数据进行修改.在标准的SQL中有3个语句,它们是INSE ...

  7. mysql中的replace

    replace字面意思是替换,在mysql里面的运用是 如下图所示 1.  replace into test values(6, 'wowowo', 'new', 'japan') 这条语句则他会正 ...

  8. Mysql怎样控制replace替换的次数?

    我想把“ABC是ABC”替换成“123是ABC”,也就是找出第一个ABC替换成123,MYSQL命令应该怎么写? UPDATE data SET body=REPLACE(body, 'ABC', ' ...

  9. 关于mysql中实现replace的sql语句

    update  表名  set  字段名=replace(字段名, '旧字符串', '新字符串')

随机推荐

  1. Python之Flask和Django框架解决跨域问题,配合附加ajax和fetch等js代码

    Flask框架py解决跨域问题示例: # -*- coding: utf- -*- # by zhenghai.zhang from flask import Flask, render_templa ...

  2. LeetCode: Construct Binary Tree from Inorder and Postorder Traversal 解题报告

    Construct Binary Tree from Inorder and Postorder Traversal Given inorder and postorder traversal of ...

  3. java基础篇---网络编程(UDP程序设计)

    UDP程序设计 在TCP的索引操作都必须建立可靠地连接,这样一来肯定会浪费大量的系统性能,为了减少这种开销,在网络中又提供了另外一种传输协议---UDP,不可靠的连接,这种协议在各个聊天工具中被广泛的 ...

  4. Android 面试知识集2

    继续上一篇文章整理有关Android的基础知识,为面试做准备的可以看看哪些知识是遗漏了.资料都是网上整理来,纠正了一些错误,有部分解析加入个人理解!感谢分享相关知识的开发者.这些知识平常开发的过程中都 ...

  5. json数据在前端(javascript)和后端(php)转换

    学习目的:前后端数据交换   思路: json数据格式是怎么样? 后端各种语言怎么将自己内容转换成json格式的内容? 前端怎么接收json数据?有几种方式? js中怎么将json数据转换成js中的数 ...

  6. mysql单表体积和一个库设计多少张表为妥

    这篇文章来自于看博客园一个园友的分享经历,原文:http://www.cnblogs.com/qqloving/p/3427138.html 他不清楚mysql一个库里面分多少张表合适,他一个库分了8 ...

  7. STM32cube库配置双ADC的同步规则采样

    http://www.stmcu.org/module/forum/forum.php?mod=viewthread&tid=605203&extra=page%3D&page ...

  8. Ubuntu中Samba的安装配置和使用

    Samba服务在Ubuntu服务器版本中默认并没有安装. 1. Samba软件包的安装 使用源安装,在终端中输入如下命令: #sudo apt-get install samba#sudo apt-g ...

  9. 【C】——itoa 函数的实现

    itoa函数的实现,函数实现功能:输入一个 int 型的数据然后修改成 十六进制的字符串. 例如:  输入 100  输出 0x64 主函数: int main(void){ ]; my_atoi(v ...

  10. ElasticSearch 深度分页解决方案 {"index":{"number_of_replicas":0}}

    常见深度分页方式 from+size es 默认采用的分页方式是 from+ size 的形式,在深度分页的情况下,这种使用方式效率是非常低的,比如 from = 5000, size=10, es ...