导读

Hi,大家好!我是白日梦!本文是MySQL专题的第 27 篇。

下文还是白日梦以自导自演的方式,围绕“如何实现记录存在的话就更新,如果记录不存在的话就插入。”展开本话题。看看你能抗到第几问吧

换一种写作风格,自导自演面试现场!感觉这样还是比较有趣的,欢迎大家订阅我的MySQL专题,公众号首发!持续更新中~

点击阅读原文,有视频串讲、视频实战各种案例、格式也会好看一点哦~

点击阅读原文,有视频串讲、视频实战各种案例、格式也会好看一点哦~

点击阅读原文,有视频串讲、视频实战各种案例、格式也会好看一点哦~

欢迎关注白日梦,公众号首发!持续连载中

推荐阅读原文,可以看视频教程!

推荐阅读原文,可以看视频教程!

推荐阅读原文,可以看视频教程!

                         


那我们继续,还是这道场景题:现在我的业务中有这样的需求:如果目标记录存在的话我就更新它,如果记录不存在的话我就插入。说说看你知道哪些实现方式吧!


嗯,比如我可以像下面这样做


这种方式。

// 伪代码user=User.FindById(1)if user == null{  user.Insert()}else{  user.Update()}



嗯!你这段代码如果不存在并发访问还好,一旦出现并发访问的情况。你这段逻辑会有诸多的并发修改异常的!


比如这样的例子:商品有上线和下线的状态,然后管理员可以在后台页面中修改商品的状态,比如代码这样写的:


// 伪代码,如果将状态置反if product.Status == “online”{   product.Status = "offline";}else{   product.Status = "online";  }




这时管理员A、B并发的去操作商品状态:




嗯,确实存在这种情况,不过我可以自定义FindById()中的sql语句,通过 select for update的方式,规避你在图画出来的风险。



比如自定义SQL,让 user = User.FindByID(1)函数执行的SQL为

select * from user where id = 1 for update;


直接select时会给id = 1的行添加一把读锁现在我通过select for update检索,在读select时给id = 的行添加写锁。


那么当我在读取使用这行数据时,其他的人select for update

就会被阻塞,因为写锁之间彼此是互


斥的。最终也不会出现Update彼此覆盖的情况




哦?那你画一个时序图出来瞧瞧


嗯嗯,时序图大概长下面这样






嗯,乍一看你上面画的这个图是没问题的但我有个问题:如果id = xx的行不存在呢?



嗯,所以我们不如分不同情况讨论一下。下面我列举不同场景,你画时序图怎样?


嗯嗯,好的! 




嗯!首先我们知道了,select for update会尝试添加X锁,也就是写锁。


常见的写锁有这么几种:

1、record lock 给指定行记录添加锁

2、gap lock 间隙锁

3、next key lock 


下面通过在不同条件下执行select for update sql,再观察实验结果,就能八九不离十的推测出for update的加锁情况。



实验一已知条件如下:

create table t(a int,b int,primary key(a),key(b));insert into t select 1,1;insert into t select 3,1;insert into t select 5,3;insert into t select 7,6;insert into t select 10,8;




select for update sql为:

select * from t where a= 5 for update;

提示:注意id=5的行已经存在了。


下面你画一下时序图吧


嗯嗯,时序图加锁情况如下:







嗯,那如果我们需要执行的sql如下:

select * from t where b = 3 for update;

注意b并不是唯一键,它只是一个普通索引哦!


你能画一下它的加锁时序图吗?


嗯嗯,时序图如下:


(最后一条Sql条件是 where a = 5,不是a=3)




还是使用这些数据

create table t(a int,b int,primary key(a),key(b));insert into t select 1,1;insert into t select 3,1;insert into t select 5,3;insert into t select 7,6;insert into t select 10,8;


假如我执行的sql是:

select * from t where a = 13 for update;


注意:

1、a=13的行并不存在哦!

2、a是唯一索引


你画一下它的时序图吧!



嗯呢,时序图如下:






还是使用这些数据

create table t(a int,b int,primary key(a),key(b));insert into t select 1,1;insert into t select 3,1;insert into t select 5,3;insert into t select 7,6;insert into t select 10,8;


假如我执行的sql是:

select * from t where a = 8 for update;


注意:

1、a=8的行并不存在哦!

2、a是唯一索引


你画一下它的时序图吧!


嗯嗯、如下:

对唯一索引来说,gap的上下都锁不住!




还是使用这些数据

create table t(a int,b int,primary key(a),key(b));insert into t select 1,1;insert into t select 3,1;insert into t select 5,3;insert into t select 7,6;insert into t select 10,8;


假如我执行的sql是:

select * from t where b = 5 for update;


注意:

1、b=5的行并不存在哦!

2、b是普通索引


你画一下它的时序图吧!


嗯嗯、如下。

对普通索引来说,gap锁会 锁上不锁下!




嗯,对的。回到我们一开始的主题:如果数据存在我们就update

数据不存在我们就insert的话题来看的话。


通过如下方案:

begin;select for update;# insert or update;commit;


是可以保证并发修改的安全性的….


嗯嗯、其实通过实验来看,确实是安全的。




小伙子可以的!整体感觉还不错。


欢迎关注我。不久会给你安排下一面


我没有问题了,你有什么想问我的吗?


暂时没有了,感谢大佬



版权声明:本文为ZhuChangwu原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/ZhuChangwu/p/14439764.html