之前做了一个功能,是对表的 CURD 操作,只是有点特殊,就是在插入/更新数据的时候因为做了批量导入。所以在插入的时候需要判断是否有相同记录存在,如果有的话则更新已经存在的数据,如果没有的话插入该条数据。

我最初设计的版本是首先查询该条记录,如果记录存在的话则更新该记录,反之则插入一条新纪录。

在单条插入/更新的时候这样做是没有问题的,但是在批量导入的环境下由于数据量过大所以这里出现里的严重的性能问题。
在这里插入图片描述
当我进行批量插入的时候,我需要对每条插入记录都要在数据库中判断是否存在相同记录,这样大大拉低了插入的性能。而为了满足存在并更新,记录是否存在这个判断又是必不可少的,那么这里怎么优化呢?

优化方案 “插入或更新” INSERT FOR UPDATE

存在则更新,不存在则插入

这里将如果存在则更新,不存在则插入的这个执行逻辑交给了 MySQL 去实现,这样我们的遍历查询插入的逻辑就统一成为了一条插入语句,无须进行判断。我们可以使用 MySQL 的条件插入语句 INSERT FOR UPDATE 实现该逻辑。
在这里插入图片描述

INSERT FOR UPDATE 语法

INSERT [LOW_PRIORITY | DELAYED | HIGH_PRIORITY] [IGNORE]
    [INTO] tbl_name
    [(col_name [, col_name] ...)]
    {VALUES | VALUE} (value_list) [, (value_list)] ...
    [ON DUPLICATE KEY UPDATE assignment_list]
 

INSERT with an ON DUPLICATE KEY UPDATE clause enables existing rows to be updated if a row to be
inserted would cause a duplicate value in a UNIQUE index or PRIMARY
KEY.

带有 ON DUPLICATE KEY UPDATEINSERT语句允许当插入一行时由于唯一索引或者主键索引而产生的重复值情况时则对已经存在的行执行更新操作。

这个语法需要配合唯一索引实现,这里我们演示使用主键ID替代。

-- 这里我们新建一张简单的用户表用作演示
CREATE TABLE user(
	id int(11) not null auto_increment primary key,
	name varchar(255) not null,
	gender varchar(32) not null default 'male',
	age int(11) not null default 0,
	version int(11) not null default 1,
	create_time timestamp not null default current_timestamp,
	update_time timestamp not null default current_timestamp on update current_timestamp
)engine="InnoDB",charset="utf8mb4";
-- 单条插入或更新的 SQL 可以这样写
-- 注意这样实现的方式不支持批量,否则就有批量记录的所有 name 都更新成了张三,gender 更新成了 male
INSERT INTO user(id,name,gender,age,version) VALUE(1,"张三","male",22,1)
ON DUPLICATE KEY UPDATE name="张三",gender="male",age=22;
-- 在批量插入或更新时我们的 UPDATE 可以通过 VALUES(field) 获取我们插入的对应记录的值
-- 如 name = VALUES(name) 则表示将 name 更新成为我们输入的值
-- 通过 VALUES() 函数获取在(VALUE|VALUES) 中映射的对应的值
INSERT INTO user(id,name,gender,age,version) VALUE(1,"张三","male",22,1)
ON DUPLICATE KEY UPDATE name=VALUES(name),gender=VALUES(gender),age=VALUES(age);
-- 这里的插入逻辑表示当存在id为1 的记录时则更新 name 为输入的name,gender 为输入的 gender ,age为输入的age

当存在重复记录时只更新指定字段的值
上面的 INSERT 语句即可实现更新部分字段的要求。下面语句同样可以实现相同功能:

-- 这里当我们的记录发生重复的时候我们只更新 age 字段,同时将我们的 version 字段加1
INSERT INTO user(id,name,gender,age) VALUE(1,"张三","male",22)
ON DUPLICATE KEY UPDATE age=VALUES(age),version=version+1;
-- 这里当我们的记录发生重复的时候我们只更新 age 字段,
-- 同时将我们的 version 字段加 1 ,这里我们又做了怪,把更新时间往后挪了一天
INSERT INTO user(id,name,gender,age) VALUE(1,"张三","male",22)
ON DUPLICATE KEY UPDATE age=VALUES(age),
version=version+1,update_time=date_add(now(),interval 1 day);

批量更新记录语句
下面我们写一个批量插入/更新的语句:

INSERT INTO user(id,name,gender,age) VALUES
(1,"张三","male",22),
(2,"李四","female",18),
(3,"王五","male",33),
(4,"赵六","male",17)
ON DUPLICATE KEY UPDATE 
name=VALUES(name),gender=VALUES(gender),age=VALUES(age),version=version+1;

在这里插入图片描述
重复执行后会发现版本不断更新,但是不会报索引重复异常。

这里使用了主键索引做演示,实际应用中开发者可以根据实际场景在需要的字段上自定义唯一索引即可。

优化方案 “删除后插入” REPLACE INTO

存在则删除并插入,不存在则直接插入

上面的条件插入或更新其实已经能够满足大部分时候的需求了,这里我们介绍另外一种实现方式。通过过删除原有的数据然后执行插入操作。

REPLACE INTO 语法

REPLACE [LOW_PRIORITY | DELAYED]
    [INTO] tbl_name
    [PARTITION (partition_name [, partition_name] ...)]
    [(col_name [, col_name] ...)]
    {VALUES | VALUE} (value_list) [, (value_list)] ...

REPLACE INTO 的语法其实和 INSERT INTO 的语法已经很像了,不同的是REPLACE INTO 在遇到由于主键或唯一键导致的重复记录时,它会首先删除已经存在的记录,然后再插入新的记录。做法有些暴力,但是却很有效。
在这里插入图片描述

-- 当 id=1 的记录存在时,会删除原有记录,然后插入我们给出的记录
REPLACE INTO user(id,name,gender,age) VALUES(1,"张三","male",22);

可以看到 REPLACE INTO相较于INSERT FOR UPDATE 代码要简单的很多,相对应的功能也简化了。简单带来的好处是对于重复记录的处理只需要将 INSERT 修改成 REPLACE 即可,无须做其他复杂的条件处理。由于新的记录与原有记录逻辑上来讲完全相同,删除并插入记录一般情况下也能够满足大多数场景。

INSERT FOR UPDATE 用法
Mysql 存在既更新,不存在就添加
MySQL 当记录不存在时insert,当记录存在时update

Update操作一定是Delete再Insert吗?Update在数据库中的执行是怎么样的?“Update操作是把数据删除,然后再插入数据”。在网上看了很多也都是这么认为的。但在查阅到一些不同看法的时候我进行了一些验证,发现还有其它的情况。这里我分三种情况来讲:1、更改没有索引列的字段,更改前和更改后的字符串长度一样;2、更改没有索引列的字段,更改后比更改前的字符串长;3、更改聚集索引字段。... 一、死锁案例MySQL版本:Percona MySQL Server 5.7.19隔离级别:可重复读(RR)业务逻辑:并发下按某个索引字段delete记录,再insert记录比如:begin;deletefromtbwhereorder_id=xxx;insertintotb(order_id)values(xxx);commit;二、MySQL锁基本概念S:共享锁(行级锁)X... 按照这个思路做完功能后去验证功能有没有问题时发现总是有些脏数据没有被删除,按理把表里的数据都查了一边,判断也正常,不应该出现数据没有被删除的清空,经过一个多小时的研究发现是我在一边查询数据库数据一边删除数据库数据的问题。最近在做一个功能,是一个清除脏数据的一个功能,去数据库中查询每一条数据,判断数据是脏数据,如果是脏数据就删除这一条数据,如果不是脏数据就保留这一条数据。刚开始思路是这样子的,分页去读取数据库中的数据,然后去判断每一页是否有脏数据,如果有脏数据就直接删除。     更新业务,待更新的数据与数据库现存的数据条数不一定相同.注意这里是不相同,也就是说可能会有新增,可能是全部更新,也可能是删除现在的部分数据;如果待更新的数据与数据库现存的数据条数相同,那只会执行全部执行更新即可,不会存在多种情况.     现在对第一种问题场景进行处理,首一种方法是全部删除现在数据,然后将待更新的记录全部进行新增.此种方法优点在于很不用考虑多种场景,并且执行简单;缺点在于对于数据量比 replace into 跟 insert 功能类似, 不同点在于:replace into 首尝试插入数据到表中, 1. 如果发现表中已经有此行数据(根据主键或者唯一索引判断)则删除此行数据,然后插入新的数据。 2. 否则,直接插入新数据。 要注意的是:插入数据的表必须有主键或者是唯一索引!否则的话,replace into 会直接插入数据,这将导... 好久没写博客了,洗完澡一时兴起,小写一篇记录sql知识点吧。 由于之前公司业务的变更,需要做一个历史数据表数据迁移到新建的数据表中,刚被分配到这个任务时,马上打开历史数据表的相关结构,一看懵逼了。需求大概是把原来的几百张数据库表(里面大概有三四类表,由于数据量太大,都进行一定规则的分表)迁移到新建的数据库表(也是采用一定规则分表)。 第一阶段,采用insert语法批量添... 1.replace into replace into t(id, update_time) values(1, now());或 replace into t(id, update_time) select 1, now();replace into 跟 insert 功能类似,不同点在于:replace into 首尝试插入数据到表中, 1. 如果发现表中已经有此行数据(根据... 一、插入数据INSERT是用来插入(或添加)行到数据库表的。插入可以用几种方式使用: 插入完整的行; 插入行的一部分; 插入多行; 插入某些查询的结果。1、简单但是不安去INSERT INTOcustomersVALUES( NULL,'person');此例子插入一个新客户到customers表。存储到每个表列中的数据在VALUES子句中给出,对每个列必须提供一个值。如果某个列没有值(如... 在MySQL数据库中,表是组成数据库的基本元素,主要用来实现存储数据记录。我们平常对表的基本操作主要是创建,修改,删除。对表里的数据进行增,删,改,查等基本操作。今天我学习了一下对表的创建、修改、删除等基础操作。如下:准备工作在学习本文章时,确保你的电脑中已经成功安装了 MySQL 。首以管理员身份打开命令提示符,输入net start mysql 启动MySQL服务,然后输入mysql -u ...