本文正在参加「金石计划 . 分割6万现金大奖」


哟,又是我小白。最近有点高产了。

连我自己都害怕了。

两个事务并发写,能保证数据唯一吗?

直接进入正题吧。


两个业务并发写,能保证数据仅有吗?

我先来解说下标题讲的是个啥。


咱们假设有这么一个用户注册的场景。用户并发恳求注册新用户。

你有一张数据库表,也便是下面的user表。

两个事务并发写,能保证数据唯一吗?

产品司理要求用户和用户之间,电话号码不能重复,为了保证这一点。咱们想到了先查一下数据库,再判别一下,假如存在,就退出,不然刺进一条数据。类似下面这样的伪代码。

select user where phone_no =2;  // 查询sql
if (user 存在) {
		return 
} else {
  insert user;   // 刺进sql
}

但这是两条sql句子,先履行查询sql,判别后再决议要不要履行刺进sql。每次用户注册的时分都会履行这么一段逻辑。

那假如,此刻有多个用户在做操作,就会并发履行这段逻辑。

假如都并发履行,第一条sql句子履行完之后,都会发现没有用户存在。此刻都履行了刺进,这样就呈现了两条相同的数据才对。

所以,有人就想了,这两条sql句子逻辑应该是一个全体,不应该拆开,所以就想到了业务,经过业务把这两个sql作为一个全体,要么一同履行,要么都回滚。

这正是数据库ACID里的A(Atomicity),原子性的完美体现啊。

两个事务并发写,能保证数据唯一吗?

伪代码类似下面这样。

begin;
select user where phone_no =2;  // 查询sql
if (user 存在) {
		return 
} else {
  insert user;   // 刺进sql
}
commit;

那么问题来了,这段逻辑,并发履行,能保证数据仅有?

当然是不能。

业务內的多条sql句子,确实是原子的,要么一同成功,要么一同失败,这没错,但跟这个场景没什么太大关系。业务是并发履行的,第一个业务履行查询用户,并不会阻塞另一个业务查询用户,所以都有或许查到用户不存在,此刻两个业务逻辑都判别为用户不存在,然后刺进数据库。业务内两条sql都履行成功了,所以就刺进了两条相同的数据。

两个事务并发写,能保证数据唯一吗?


怎样保证数据仅有?

那么咱们接下来聊聊,怎样保证上面这种场景下,刺进的数据是仅有的。办法有很多种,但咱们今日只讨论mysql内部的做法,不考虑其他外部中间件(比方redis分布式锁这些)。


仅有索引

经过下面的指令,能够为数据库user表的phone_no字段参加仅有索引。

ALTER TABLE `user` ADD unique(`phone_no`);

咱们履行一条写操作时,比方下面这句,

INSERT INTO `user` (`user_name`, `phone_no`) VALUES('小红', 2);

第一次会刺进成功,第二次再履行刺进,则会呈现报错。

Duplicate entry '2' for key 'phone_no'

含义是phone_no这个字段是仅有的,加两次phone_no=2会导致重复。

所以乎回到咱们文章最初的场景里,就完美解决了重复刺进的问题了。


那么问题来了。


为什么仅有索引能保证数据仅有?

咱们看看一句写操作,会阅历什么。

两个事务并发写,能保证数据唯一吗?

首先,mysql作为一个数据库,内部首要分为两层,一层是server层,一层是存储引擎层(一般是innodb)。

server层首要管的是数据库链接,权限校验,以及sql句子校验和优化之类的工作。恳求打到存储引擎层,才是真实的查询和更新数据的操作。

我们都知道数据库是耐久化存储,且最终都是把数据存到磁盘上的。

那数据库读写是直接读写磁盘数据吗?

不是,假如直接读写磁盘的话,那就太慢了,为了提高速度。

它在磁盘前面加了一层内存,叫buffer pool。它里边有很多细节,但最首要的便是个双向链表,里边放的是一个个数据页,每个数据页的大小默许是 16kb,数据页里边放的便是磁盘的数据。

两个事务并发写,能保证数据唯一吗?

所以有了这层buffer pool内存,mysql的读和写操作都能够先操作这部分内存,假如想要读写的数据页不在buffer pool里,再跑到磁盘里去捞。由于读写内存的速度比读写磁盘快得多

所以引擎读写都快多了。

但这还不行,很多时分写操作,我的诉求便是把xx更新为xx,或刺进xx,数据库光知道这一点就够了,我底子不需要知道数据页本来长什么姿态。

有点笼统?举个比如吧。

比方说我想要把id=1的这条数据的phone_no字段更新为100,数据库知道这一点就够了,至于这条数据本来phone_no究竟是等于20,仍是30,这底子不重要,反正最终都会变成我想要的phone_no=100。

也便是说,假如有那么一块内存,记录下我预备把数据改成什么姿态,然后后续异步慢慢更新到磁盘数据上。那我甚至到不需要在一开始就把这块数据从磁盘读到buffer pool中,按照这个思路,change buffer就来了。

两个事务并发写,能保证数据唯一吗?

所以乎,写加了普通索引的数据,它只要把想要写的内容写到change buffer上,就立马完毕返回了。后面innodb引擎拿着这个change buffer,再异步读入磁盘数据到内存,将change buffer的数据修改到数据页中,再写回磁盘,这速度就上来了,秒啊。

但这个change buffer,放在仅有索引这里就不管用了,毕竟,它得保证数据真的只有一条,那就得去看下数据库里,是不是真的有这条数据。

所以,对于insert场景,普通索引把需求扔到change buffer就完事返回了,而仅有索引需要真的把数据从磁盘读到内存来,看下是不是有重复的,没重复的再刺进数据。

这仅有索引,在功能上就输了一截了。

所以回到仅有索引为什么能保证数据仅有的问题上,一句话概括便是,仅有索引会绕过change buffer,保证把磁盘数据读到内存后再判别数据是否存在,不存在才干刺进数据,不然报错,以此来保证数据是仅有的。


总结

  • 加仅有索引能够保证数据并发写入时数据仅有,并且最省劲省心。
  • 数据库经过引入一层buffer pool内存来提高读写速度,普通索引能够利用change buffer提高数据刺进的功能。
  • 仅有索引会绕过change buffer,保证把磁盘数据读到内存后再判别数据是否存在,不存在才干刺进数据,不然报错,以此来保证数据是仅有的。

给我们留个问题呗,前面也提到了,innodb中,利用了change buffer,为普通索引做了加快有没有哪些场景下,change buffer不仅不能给普通索引加快,还起到反作用的呢?

最终

我们也别笑,文章最初提到的经过开业务来保证数据仅有性的错误操作,其实很简单犯,并且我曾经也遇到过不止一次这样的工作。

做这个操作的人,还会信誓旦旦,言之凿凿的说出他的理解,在我解说了几遍发现无果之后,我挑选垂头伪装考虑,然后说:”你说的有点道理,我再回去好好想想”,然后默默的为数据表加上仅有索引……

我相信对方必定已司理解了。那一刻,我感觉我写的不是代码,我写的是人情世故。

两个事务并发写,能保证数据唯一吗?


假如文章对你有协助,欢迎…..

算了。


别说了,一同在常识的海洋里呛水吧