MySQL事务概念简要(2)

回顾

1.标准事务四个特性ACID

  • 原子性(atomicity):最小工作单元,要么都成功,要么都失败回滚。
  • 一致性(consistency):数据库总是从一个一致性状态转换到另外一个一致性的状态,失败事务中的改动不会保存到数据库中
  • 隔离性(isolation):一个事务的修改在最终提交前,对其他事务是不可见的。
  • 持久性(durability):一旦事务成功提交,其修改就会永久保存到数据库中。

2.隔离级别

  • read uncommitted
  • read committed
  • repeatable read
  • serializable

3.多版本控制(Multiversion Concurrency Control)

  • 指的是一种提高并发的技术。最早的数据库系统,只有读读之间可以并发,读写,写读,写写都要阻塞。引入多版本之后,只有写写之间相互阻塞,其他三种操作都可以并行,这样大幅度提高了InnoDB的并发度。在内部实现中,InnoDB通过undo log保存每条数据的多个版本,并且能够找回数据历史版本提供给用户读,每个事务读到的数据版本可能是不一样的。在同一个事务中,用户只能看到该事务创建快照之前已经提交的修改和该事务本身做的修改。
  • MVCC在 Read Committed 和 Repeatable Read两个隔离级别下适配起作用

MVCC具体细节

1.隐藏字段

1.DB_TRX_ID(6字节):表示最近一次对本记录行作修改(insert | update)的事务ID。至于delete操作,InnoDB认为是一个update操作,不过会更新一个另外的删除位,将行表示为deleted。并非真正删除。
2.DB_ROLL_PTR(7字节):回滚指针,指向当前记录行的undo log信息
3.DB_ROW_ID(6字节):随着新行插入而单调递增的行ID。理解:当表没有主键或唯一非空索引时,innodb就会使用这个行ID自动产生聚簇索引。如果表有主键或唯一非空索引,聚簇索引就不会包含这个行ID了。
4.deleted_bit:删除标记位

2.快照read view(snapshot)

里面保存了【对本事务不可见的其他活跃事务】,主要用来做可见性判断的(即判断事务能select哪些行)

read view中的几个关键字段:

  • createor_trx_id:当前事务的id(分配给事务的id是递增的编号)
  • trx_ids:readView创建时其他未提交的活跃事务id列表(逆序排列)
  • low_limit_id:目前出现过的最大的事务ID + 1,即下一个即将被分配的事务ID
  • up_limit_id:活跃事务列表trx_ids中最小的事务id,如果trx_ids为空的话,则up_limit_id= low_limit_id

3.undo log

  • 事务的回滚日志,存储的是老版本数据,当一个事务需要读取记录行时,如果当前记录行不可见,可以顺着undo log链找到满足其可见性条件的记录行版本。
  • 可见性算法主要就是靠undo log来实现的
  • undo log分类
    1.insert undo log : 事务对insert新记录时产生的undo log, 只在事务回滚时需要, 并且在事务提交后就可以立即丢弃。
    2.update undo log : 事务对记录进行delete和update操作时产生的undo log,不仅在事务回滚时需要,快照读也需要。提交后仍然留着需要,有个purge线程专门处理这类undo log的清理工作。
  • purge线程:
    为了实现InnoDB的MVCC机制,更新或者删除操作都只是设置一下旧记录的deleted_bit,并不真正将旧记录删除。为了节省磁盘空间,InnoDB有专门的purge线程来清理deleted_bit为true的记录。purge线程自己也维护了一个read view,如果某个记录的deleted_bit为true,并且DB_TRX_ID相对于purge线程的read view可见,那么这条记录一定是可以被安全清除的。(只有当系统没有比这个log更早的read-view了的时候才能删除)
  • update分为两种情况:update的列是否是主键列。
    1.如果不是主键列,在undo log中直接反向记录是如何update的。即update是直接进行的。
    2.如果是主键列,update分两部执行:先删除该行,再插入一行目标行。
  • 每次对记录进行改动,都会记录一条undo日志,每条undo日志也都有一个DB_ROLL_PTR属性(INSERT操作对应的undo日志没有该属性,因为该记录并没有更早的版本),可以将这些undo日志都连起来,串成一个链表,所以现在的情况就像下图一样:


    image.png

    如图,事务T2对字段做修改的步骤:
    1.事务T2对该行加排他锁
    2.然后把该行数据拷贝到undo log中,作为旧版本
    3.拷贝完毕后,修改该行的字段,并且修改DB_TRX_ID为T2, 回滚指针指向拷贝到undo log的旧版本。(然后还会将修改后的最新数据写入redo log)
    4.事务提交,释放排他锁
    从上面可以看出,不同事务或者相同事务的对同一记录行的修改,会使该记录行的undo log成为一条链表,undo log的链首就是最新的旧记录,链尾就是最早的旧记录。

4.可见性算法

在innodb中RR事务隔离级别情况下,创建一个新事务后,执行第一个select语句的时候,innodb会创建一个快照(read view),快照中会保存系统当前不应该被本事务看到的其他活跃事务id列表(即trx_ids)。当用户在这个事务中要读取某个记录行的时候,innodb会将该记录行的DB_TRX_ID与该Read View中的一些变量进行比较,判断是否满足可见性条件。

假设当前事务要读取某一个记录行,该记录行的DB_TRX_ID(即最新修改该行的事务ID)为trx_id,可见性算法流程图(low_limit_id=max_trx_id+1=current_system_trx_id):


image.png

5.Read Committed 与 Repeatable Read的快照区别

  • 在innodb中的Repeatable Read级别, 只有事务在begin之后,执行第一条select(读操作)时, 才会创建一个快照(read view),将当前系统中活跃的其他事务记录起来;并且事务之后都是使用的这个快照,不会重新创建,直到事务结束。
  • 在innodb中的Read Committed级别, 事务在begin之后,执行每条select(读操作)语句时,快照会被重置,即会重新创建一个快照(read view)。
  • 这个区别从而也就导致了RR情况下能保证一致性读,因为快照始终是用固定初始select创建的快照

6.当前读和快照读

  • 快照读(snapshot read):普通的 select 语句(不包括 select ... lock in share mode, select ... for update)
  • 当前读(current read) :select ... lock in share mode,select ... for update,insert,delete 语句(这些语句获取的是数据库中的最新数据,官方文档:14.7.2.4 Locking Reads

只靠 MVCC 实现RR隔离级别,能防止快照读的幻读,不能防止当前读的幻读
1.比如事务A开始后,执行普通select语句,创建了快照;之后事务B执行insert语句;然后事务A再执行普通select语句,得到的还是之前B没有insert过的数据,因为这时候A读的数据是符合快照可见性条件的数据。这就防止了部分幻读,此时事务A是快照读。
2.但是,如果事务A执行的不是普通select语句,而是select ... for update等语句,这时候,事务A是当前读,每次语句执行的时候都是获取的最新数据。也就是说,在只有MVCC时,A先执行 select ... where nid between 1 and 10 … for update;然后事务B再执行 insert … nid = 5 …;然后 A 再执行 select ... where nid between 1 and 10 … for update,就会发现,多了一条B insert进去的记录。这就产生幻读了,所以单独靠MVCC并不能完全防止幻读。

  • 所以说,RR(read repeatable)隔离级别下,仅仅依赖MVCC的话,可以保证可重复读,还能防止部分幻读,但不能完全防止幻读(因为如果是当前读的情况下,获取的是最新数据,仍然会有幻读现象)。
  • InnoDB在实现RR隔离级别时,是通过“行级锁+MVCC”来彻底做到完全防止幻读的:不仅使用了MVCC,还会对“当前读语句”读取的记录行加记录锁(record lock)和间隙锁(gap lock),禁止其他事务在间隙间插入记录行,来防止幻读。(锁的概念后文会讲)

表级锁与行级锁

  • 表级锁:具有开销小、加锁快的特性;表级锁的锁定粒度较大,发生锁冲突的概率高,支持的并发度低;
  • 行级锁:具有开销大,加锁慢的特性;行级锁的锁定粒度较小,发生锁冲突的概率低,支持的并发度高。

共享锁与排他锁

  • 共享锁(S):允许获得该锁的事务读取数据行(读锁),同时允许其他事务获得该数据行上的共享锁,并且阻止其他事务获得数据行上的排他锁。
  • 排他锁(X):允许获得该锁的事务更新或删除数据行(写锁),同时阻止其他事务取得该数据行上的共享锁和排他锁。

意向锁

demo理解:
1.事务A锁住了表中的一行,让这一行只能读,不能写。
2.之后,事务B申请整个表的写锁。如果事务B申请成功,那么理论上它就能修改表中的任意一行,这与A持有的行锁是冲突的。
3.数据库需要避免这种冲突,要让B的申请被阻塞,直到A释放了行锁。 数据库要怎么判断这个冲突呢?
•step1:判断表是否已被其他事务用表锁锁表
•step2:判断表中的每一行是否已被行锁锁住。 (这样的判断方法效率不高,因为需要遍历整个表)
于是就有了意向锁。在意向锁存在的情况下,事务A必须先申请表的意向共享锁,成功后再申请一行的行锁。在意向锁存在的情况下, 上面的判断可以改成
•step1:不变
•step2:发现表上有意向共享锁,说明表中有些行被共享行锁锁住了,因此,事务B申请表的写锁会被阻塞。
•注意:申请意向锁的动作是数据库完成的,就是说,事务A申请一行的行锁的时候,数据库会自动先开始申请表的意向锁,不需要我们程序员使用代码来申请。 总结:为了实现多粒度锁机制(为了表锁和行锁都能用)

  • “The main purpose of IX and IS locks is to show that someone is locking a row, or going to lock a row in the table.” 意思是说加意向锁的目的是为了表明某个事务正在锁定这个表的一行或者将要锁定一行。

意向锁属于表级锁,由 InnoDB 自动添加,不需要用户干预。意向锁也分为共享和排他两种方式:

  • 意向共享锁(IS):事务在给数据行加行级共享锁之前,必须先取得该表的 IS 锁。
  • 意向排他锁(IX):事务在给数据行加行级排他锁之前,必须先取得该表的 IX 锁。

表级锁和表级意向锁的兼容性:


image.png

行级锁的实现

  • Innodb通过给索引上的索引记录加锁的方式实现行级锁
  • 行级锁分为三种
    1.记录锁(Record Lock)
    2.间隙锁(Gap Lock)
    3.临键锁(Next-key Lock)

1. 记录锁(Record Lock)

  • 记录锁(Record Lock)是针对索引记录(index record)的锁定。
  • 记录锁永远都是锁定索引记录,锁定非聚集索引会先锁定聚集索引,然后再锁定非聚集索引。如果表中没有定义索引,InnoDB 默认为表创建一个隐藏的聚簇索引,并且使用该索引锁定记录。

1.例如,id为唯一主键;SELECT * FROM t WHERE id = 1 FOR UPDATE;会阻止其他事务对表 t 中 id = 1 的数据执行插入、更新,以及删除操作:

  • 表t上会有一个IX锁
  • 主键索引id_index上对应记录会有一个X锁

2.例如,id为唯一主键,name是加了普通索引的字段;SELECT * FROM t WHERE name = ‘test’ FOR UPDATE;会阻止其他事务对表 t 中 name = ‘test’ 的数据执行插入、更新,以及删除操作:

  • 表t上会有一个IX锁
  • 主键索引id_index上对应记录会有一个X锁
  • 索引name_index上对应记录会有一个X锁

2.间隙锁(Gap Lock)

  • 间隙锁(Gap Lock)锁定的是索引记录之间的间隙、第一个索引之前的间隙或者最后一个索引之后的间隙。严格来说是个开区间
  • 只会在Repeatable Read下使用

1.例如,SELECT * FROM t WHERE c1 BETWEEN 1 and 10 FOR UPDATE;会阻止其他事务将 1 到 10 之间的任何值插入到 c1 字段中,即使该列不存在这样的数据;(c1=4和c1=1是存在的,其他都不存在)因为这些值都会被锁定:
•表 t 上存在 IX 锁
•主键索引上存在 1 个 X 记录锁(id = 1)
•主键索引上存在 2个间隙锁(1, 4],(4,10] 后面可以得知,其实这个属于next-key锁了

3.临键锁(Next-key Lock)

理解为记录锁+间隙锁即可,间隙锁的边缘的索引记录恰好有个记录锁

SELECT * FROM t WHERE c1 BETWEEN 1 and 10 FOR UPDATE;c1是个无索引的字段的话,那么就会导致出现( -infinity ,1),1,(1,10),10,(10, infinity )的锁。会导致任何插入都需要等待锁释放;因为从c1跟主键索引构建不了管联,所以索引一定要加好来

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

推荐阅读更多精彩内容