C-C++后端开发学习18Mysql事务原理.docx

C/C++后端开发学习18Mysql事务原理文章目录1事务基本概念oACID特性o原子性(A)一致性(C)隔离性(I)持久性(D)o事务并发异常o1)脏读2)不可重复读3)幻读o隔离级别o1)READUNCOMMITTED2)READCOMMITTED3)REPEATABLEREAD4)SERIALIZABLE不同隔离级别对比o事务控制语句2事务隔离性的实现o2.1锁o2.1.1锁的类型1)共享锁(S锁,读锁)2)排他锁(X锁,写锁)3)意向共享锁(IS锁)4)意向排他锁(IX锁)2.1.2锁的具体实现1)RecordLock(行锁)2)GapLock(间隙锁)3)Next-KeyLock4)InsertIntentionLock(插入意向锁)5)AUTO-INCLock(自增锁)2.1.3加锁的对象示例示例1示例2o2.2MVCCoMVCC的实现——undolog3事务持久性的实现——redolog1事务基本概念事务将数据库从一种一致性状态转换为另一种一致性状态。它是访问并更新数据库各种数据项的一个程序执行单元,可由一条非常简单的SQL语句组成,也可以由一组复杂的SQL语句组成。

简而言之,在数据库提交事务时,可以确保要么所有修改都已经保存,要么所有修改都不保存。在MySQLInnoDB下,每一条语句默认都是事务。可以通过setautocommit=0;设置当前会话手动提交。ACID特性原子性(A)事务操作要么都做(提交),要么都不做(回滚)。事务的回滚的通过undolog来实现的。undolog记录的是事务每步具体操作,当回滚时,执行事务具体操作的逆运算。一致性(C)在事务执行前后,数据库的完整性约束没有被破坏。一致性由原子性、隔离性以及持久性共同来维护的。例如:一个表的姓名**一键,如果一个事务对姓名**修改,但是在事务提交或事务回滚后,表中的姓名**不唯一了,这样就破坏了一致性隔离性(I)对于同时执行的多个不同事务,其操作对象互相隔离,也就是说一个事务提交前对其他事务都不可见。这主要是通过MVCC(多版本并发控制)和锁来实现的。锁用来处理并发DML操作。而MVCC实现一致性非锁定读,通过记录和获取行版本,而不是使用锁来限制读操作,从而实现高效并发读性能。持久性(D)事务提交后,事务DML操作将会持久化,即使发生宕机等故障,数据库也能将数据恢复。这是通过写入redolog磁盘文件来实现的,大致内容包括操作了数据的哪一个页、页偏移值以及具体数据等。

事务并发异常在我们未对事务做优化前,事务的并发操作可能存在如下问题。1)脏读事务(A)可以读到另外一个事务(B)中未提交的数据,则称事务A读到了脏数据。2)不可重复读一个事务还未提交前,两次读同一个数据得到的结果却不一样的情况。出现这种情况说明有另一个事务提交了修改。一般而言,不可重复读的问题是可以接受的,因为读到已经提交的数据,一般不会带来很大的问题。3)幻读往往发生在事务中需要先读数据进行条件判断然后再进行写操作的情况。读数据后,判断条件成立,然后在执行写操作前,数据已被其他事务修改,之前读操作得到的条件已经不满足,导致后续写操作失败或错误。也就是说读了个”寂寞“。例如:对于一个以name为唯一索引的表,在一个事务中经查询得知不存在name=aaa的行记录,于是正准备执行插入name=aaa的行记录,此时另外一个事务执行了相同的插入操作,这将导致本事务的此次插入操作失败。幻读也可以通过手动给读操作加锁来解决,但具体还需要看隔离级别。隔离级别引入隔离级别的目的就是为了解决上面提到的事务并发异常。SQL标准制定了四种事务隔离级别的标准,各数据库厂商在正确性和性能之间做了妥协,并没有严格遵循这些标准。

1)READUNCOMMITTED所谓读未提交,即可以读到其他事务未提交的操作。该级别下读操作不加锁,写操作加排他锁,在事务提交或回滚后释放锁;2)READCOMMITTED读已提交,即只能读到已提交的数据,但可能出现”不可重复读“。从该级别开始支持MVCC,也就是提供一致性非锁定读。此时读取操作实际读取的是历史快照数据的最新版本,如果出现更新的版本,则读到的就是更新的版本了。很多厂商(如Oracle、SQLServer)默认隔离级别就是READCOMMITTED。3)REPEATABLEREAD可重复读,可以避免”不可重复读“的问题,即事务提交前读同一个数据得到的结果总是相同的。此时执行读操作实际读到的是该事务开始时的快照版本(不是最新版本),因为事务开始时刻的那个快照版本肯定不会变,所以每次读的结果都是一样。该级别下也支持MVCC,但如果不主动加锁仍可能出现幻读。4)SERIALIZABLE可串行化,即所有事务都是串行化的执行,因此是最严苛的隔离级别。可以解决上面的所有的并发异常。不同隔离级别对比MySQLInnoDB默认支持的隔离级别是REPEATABLEREAD。事务控制语句设置事务隔离级别的命令为:--设置隔离级别SET[GLOBAL|SESSION]TRANSACTIONISOLATIONLEVELREPEATABLEREAD;开始和提交事务等:--显示开启事务STARTTRANSACTION--提交事务,并使得已对数据库做的所有修改持久化COMMIT--回滚事务,结束用户的事务,并撤销正在进行的所有未提交的修改ROLLBACK--创建一个保存点,一个事务可以有多个保存点SAVEPOINTidentifier--删除一个保存点RELEASESAVEPOINTidentifier--事务回滚到保存点ROLLBACKTO[SAVEPOINT] identifier 示例: DROPTABLEIFEXISTS`lock_test`; CREATETABLE`lock_test`(`id`INTPRIMARYKEYAUTO_INCREMENT, `cid`INT, `num`INT,-- 用于测试修改 KEY(`cid`) )ENGINE=InnoDB; -- 建表 INSERTINTO`lock_test`(`cid`,`num`)VALUES(3,1),(5,2),(9,3),(11,4),(15,5);-- 设置隔离级别并开始事务 SETSESSIONTRANSACTIONISOLATIONLEVELREPEATABLEREAD;-- 隔离级别 REPEATABLE READ STARTTRANSACTION;SELECT*FROM`lock_test`WHERE`cid`>9;-- 读操作 -- ... COMMIT;-- 提交事务 2 事务隔离性的实现2.1 锁 锁用于实现事务的隔离级别。

Mysql 的锁丰富而复杂,多个概念互相联系,不易理解。 2.1.1 锁的类型 首先Mysql 针对事务的锁粒度可分为三种层次:  针对表加锁(即对B+树加锁)  针对页加锁(即对B+树叶子节点加锁)  针对行加锁(即对B+树叶子节点当中的某一记录行加锁) 具体来说,共享锁和排他锁是行级锁,而意向共享锁和意向排他锁都是表级锁。所谓“意向”,大致意思就是说事务有意向对表中的某些行加锁。 1)共享锁(S 锁,读锁) 针对事务的读操作所加的锁,并且是针对某一行加锁:  在SERIALIZABLE 隔离级别下,默认帮读操作加共享锁(即自动加锁);  在REPEATABLE READ 隔离级别下,若需要解决幻读问题,需手动加共享锁; 在READ COMMITTED 隔离级别下,可以加共享锁但没必要加,采用的是 MVCC(见2.2);  在READ UNCOMMITTED 隔离级别下,既没有加锁也没有使用 MVCC(见2.2); 其中,REPEATABLE READ 和READ COMMITTED 都可以加共享锁但二者的具体实现不同,READ COMMITTED 采用的是“行锁”,而REPEATABLE READ 采用的是“Next-Key 锁”,具体见2.1.2。

需要手动加共享锁时,在执行语句末尾加LOCK IN SHARE MODE 指令。 2)排他锁(X 锁,写锁) 针对事务的删除或更新操作加的锁,并且是针对某一行加锁。 在4 种隔离级别下,事务写操作都自动添加了排他锁,事务提交或事务回滚后释放锁。 需要手动加排他锁时,在执行语句末尾加FOR UPDATE 指令。 3)意向共享锁(IS 锁) 对一张表中某几行加的S 锁。事务要获取表中某些行的S 锁,必须先获得表的IS 锁。同时,在某个表中插入S 锁时,则自动在页和表上添加IS 锁。4)意向排他锁(IX 锁) 对一张表中某几行加的X 锁。事务要获取表中某些行的X 锁,必须先获得表的IX 锁。同时,在某个表中插入X 锁时,则自动在页和表上添加IX 锁。普通select 查询语句默认不加锁,而DML 操作(增、删、改)默认加排他锁。隔离级别与加行级锁操作的关系汇总:2.1.2 锁的具体实现 锁可以锁住单行记录,也可以锁住各个记录的索引之间的一段范围(比如要向某一个索引范围内插入一个新记录时)。 1)Record Lock(行锁) 又叫记录锁,不论读写,都是对单行记录加锁,实质是对主键索引加锁。

2)Gap Lock(间隙锁) 间隙锁会锁定一个索引范围,但不包含各个已存在的记录的索引本身。也就是说,对于前面第1 节中所举的示例,下面的语句(在REPEATABLE READ 级别下手动加锁),间隙锁会锁住大于指定值的的所有索引范围,用区间表示为:(9, 11),(11, 15),(15, +∞),都是全开区间。 SELECT*FROM`lock_test`WHERE`cid`>9LOCKINSHAREMODE; 实际上,在READ COMMITTED 级别下,还会自动把各个间隙间的索引也加上行锁,于是锁住的索引范围区间变为:(9, 11],(11, 15],(15, +∞),成了左开右闭区间,这实际上就是接下来要说的Next-Key Lock。 REPEATABLE READ 及以上的隔离级别才支持间隙锁,这就是为什么在READ COMMITTED 隔离级别下即使手动加锁也不能解决幻读问题,因为在READ COMMITTED 隔离级别下即使使用范围查询加的也是行锁,只锁住了单个行,没有锁住一个范围。 问1:什么情况下即使在REPEATABLE READ 级别也只加行锁而不加间隙锁呢? 在索引连续的情况下。

比如前面的例子中我们再插入一个记录的cid 为10,则存在连续的索引 9、10、11,那么 9 和 10 之间就不存在间隙,不会加间隙锁,10 和11 之间也会不加,因此对于索引10 和11 只有行锁。 3)Next-Key Lock实质是Record Lock + Gap Lock,锁定相邻索引间的范围,同时会锁住范围内各个记录本身。主要目的是阻止多个事务将记录插入到同一个范围内,从而避免幻读。所谓“Next-Key”想表达的意思大概就是说锁的范围包含下一个索引key。 问2:那什么情况下一个间隙锁不会变成 Next-Key Lock 呢? 查询条件为“等于”时,1)查询的索引对应记录不存在,只有间隙锁;2)查询

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享