数据库中的二阶段提交和二阶段锁

二阶段锁

对于Innodb中的行锁,实际上是在需要的时候才加,但并不是不需要了就立即释放,而是等着事务结束了才会释放。其实这句话就是二阶段锁的含义。因此,如果再事务中需要锁多个行,需要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放,这样尽可能减少持有锁的时间。

现在举个例子:需要在美团app上买一张故宫的门票

  1. 从顾客的账户中扣除门票钱
  2. 给美团的账户中增加门票钱
  3. 记录一条交易log

从上面的例子中可以看到,现在需要update两条记录,insert一条记录。事务是一定需要的,不然一旦某条sql失败,会导致数据不一致问题。那么这三条数据如何安排顺序呢?

按照上面二阶段锁的定义,相关行级锁应该持有时间最小,所以这种背景下,两个update一定要放在最后,第三条放在第一。这似乎就已经是比较好的解决方案。

那下面这个例子:

现在要删除10000行数据,有以下几种方案:

  1. 执行sql:delete from T limit 10000;
  2. 一个连接中循环20次 delete from T limit 500;
  3. 20个连接中执行delete from T limit 500;

这几种方法哪个比较好呢?

  1. 根据二阶段锁,占用这些行级锁的时间很长,导致其他客户端等待资源时间较长
  2. 长事务分成短事务,每次事务占用锁的时间比较短,其他客户端等待时间较短,可以提高并发度
  3. 人为制造所竞争,加剧并发量,实际意义不大

二阶段提交

二阶段提交涉及到一个更新sql语句的处理流程和redo log和binlog

现在看下一条更新sql是如何执行的

CREATE TABLE `new_table` (
  `id` int(11) NOT NULL,
  `num` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

现在要把id=1的这一行+1,会这么写

update new_table set num = num + 1 where id=1

相关执行流程

sql执行流程

涉及到update操作的时候,会涉及到redo log和binlog

redo log

每次更新的时候,可以想一想,其实并不是每次都更立刻更新到磁盘上了,这样的话IO很频繁,响应也会很慢,为了解决这个问题,出现了redo log。

当有一条记录要更新的时候,会先把记录写进redo log里面,并更新内存,这个时候更新就算完成了,就可以返回客户端了。同事,innodb会在适当的时候,将这个操作记录更新到磁盘里面,而这个往往是系统比较空闲的时候做的。但是如果系统更新的频率比较高,redo log写满了,该怎么办?这个时候只能停止更新,先把redo log的内容刷到磁盘里面,把redo log清空

可以想象,把redo log里面的数据刷到磁盘这个过程是很痛苦的,因为磁盘IO比较高,所以这个时候所有的查询更新操作,都会有抖动,这种情况下也叫做刷脏页,相关刷脏页就不介绍了,可以看下面这个介绍

https://gsmtoday.github.io/2019/02/08/flush/

binlog

从上面执行流程的图可以看到,一个sql执行过程中,主要分为客户端、server端、存储引擎。redo log是存储引擎innodb特有的日志,binlog是server端特有的日志。所以说,在使用过程中,如果数据库异常重启,之前提交的记录会在redo log中,因为redo log是存储引擎级别的,所以记录不会丢失,这个也叫做crash-safe

那么为啥会有两部分日志?myIsam出现的比innodb早,之前在server端是有binlog的,binlog做的是归档工作,,归档工作自然是没有crash-safe能力,所以后来innodb用了redo log来实现crash-safe能力。那么这两个日志主要的区别是什么呢?

  1. redo log是存储引擎级别的,是innodb特有的;binlog是server端持有的,是所有引擎都有的功能
  2. redo log是物理日志,记录的是在某个数据页做了什么修改;binlog是逻辑日志,记录的是这个语句的原始逻辑,如果是statement格式的话,记录的是sql语句,如果是row格式的话,记录的是行的内容,包含更新前更新后的
  3. redo log是循环写的,空间固定(一般是4个g,1个g为一组);binlog是可以追加写入的,就是说binlog文件写完会写入下一个文件,不会覆盖之前的记录

下面介绍一下上面sql语句具体的执行流程

  1. 首先找到id=1这行。因为id是主键,所以直接走主键索引树查找到对应的记录即可
  2. 看下当前数据所在数据页是否在内存中,如果在内存中,直接返回给执行器,如果没有再内存中,需要把当前数据页加载到内存中。还要看数据页加载到内存后容量是否足够,如果不够,需要刷脏页
  3. 执行器拿到存储引擎给的数据,把这个值+1,得到新的数据再写会存储引擎
  4. 存储引擎把这部分数据更新到内存中,同时将这个更新记录到redo log中(在第几个数据页把某个数据改为某个数据),此时redo log处于prepare状态,告诉执行器执行完成,随时可以提交事务
  5. 执行器生成这个操作的binlog,把binlog写入磁盘
  6. 执行器调用引擎的提交事务接口,引擎把刚刚写入的redo log改为commit状态

其实前3步应该还比较好理解,后三步事务结束的时候,分成了两个状态处理了

二阶段提交

为什么要把事务的commit分成两个阶段呢?从prepare->commit。这个主要是为了让binlog和redo log的状态是一致的。

如果不用二阶段提交会是什么结果呢?是数据被存储引擎更新到内存后,是先写binlog还是先commit redo log呢?

  1. 先写binlog。如果在binlog写完以后,server端crash,这就导致redo log还没写,那么如果系统恢复后,redo log并没有记录这行数据有改动,但是binlog记录了这行数据+1,这就导致binlog恢复后多了一个事务,和原来库中的值已经不同了
  2. 先commit redo log。redo log commit后,mysql进程异常重启,binlog写入失败,日志备份就没这个变化了。那以后如果使用binlog恢复长期的数据,那就跟真实的数据出现出入了。

所以可见,如果不用二阶段提交,怎么样都有可能出现数据不一致。那么为啥二阶段可以保证?

上面第五步结束后,系统server端crash没有通知到存储引擎,redo log没有写入,但是prepare和binlog完整,重启后会自动commit。也就是说这个时候保存的是修改后的数据

上面第四步结束后,由于没有写入binlog,所以会直接回滚。这样binlog和redo log就一致了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 什么是事务 事务是一条或多条数据库操作语句的组合,具备ACID,4个特点。 原子性:要不全部成功,要不全部撤销 隔...
    jiangmo阅读 1,078评论 0 3
  • 最近碰到几个业务场景,会遇到并发的问题。在单实例情况下,我们会通过java.util.concurrent包...
    菜鸟小玄阅读 2,250评论 0 5
  • 我们就从一条 update 语句开始。 其实执行流程和查询流程一致,只是最后执行器执行的是找到这条数据,并进行更新...
    帅气的名称被占用阅读 1,609评论 2 4
  • 12.19日精进:敬畏—进入—体验—交给—持续 1,缺啥补啥,怕啥练啥; 2,一切为我所用,所用为团队家; 3,我...
    dangdangda_a512阅读 191评论 0 0
  • 又是一年芳草绿,依然好景舞春风。在这充满生机、萌发活力的美好季节,我们对新的一年怀抱新的憧憬,寄予新的期待。春天,...
    乔一尘阅读 611评论 0 1