两个事务并发写,能保证数据惟一吗?
我先来解释下题目讲的是个啥。
咱们假如有这么一个用户注册的场景。用户并发申请注册新用户。
你有一张数据库表,也就是上面的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不仅不能给一般索引减速,还起到副作用的呢?
最初
大家也别笑,文章结尾提到的通过开事务来保证数据唯一性的错误操作,其实很容易犯,而且我已经也遇到过不止一次这样的事件。
做这个操作的人,还会山盟海誓,言之凿凿的说出他的了解,在我解释了几遍发现无果之后,我抉择抬头伪装思考,而后说:”你说的有点情理,我再回去好好想想”,而后默默的为数据表加上惟一索引……
我置信对方必定曾经了解了。那一刻,我感觉我写的不是代码,我写的是人之常情。
如果文章对你有帮忙,欢送…..
算了。
别说了,一起在常识的陆地里呛水吧
发表回复