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