当前位置: 首页 > 后端技术 > Python

mysql事务的实现原理

时间:2023-03-25 21:50:26 Python

本文是对mysql事务的总结。基本涵盖了mysql事务相关的所有知识点。面试题无外乎这些。在执行过程中有个整体的理解,如下图,MySQL服务器的逻辑架构从上到下可以分为三层:(1)第一层:处理客户端连接,授权认证等.(2)第二层:服务器层,负责解析、优化、缓存和实现查询语句的内置函数和存储过程。(3)第三层:存储引擎,负责MySQL中数据的存储和提取。MySQL中的服务器层不管理事务,事务由存储引擎实现。MySQL中支持事务的存储引擎有InnoDB和NDBCluster,其中InnoDB应用最为广泛;其他存储引擎不支持事务,例如MyIsam和Memory。具体过程图中都标??出了,看一看就够了。下面我们将典型的MySQL事务一一总结如下:starttransaction;...#oneormoresqlstatementscommit;其中starttransaction标记事务的开始,commit提交事务,并将执行结果写入数据库。如果sql语句执行有问题,会调用rollback回滚所有成功执行的sql语句。当然你也可以直接在事务中使用rollback语句来回滚。自动提交MySQL默认使用自动提交模式,如下所示:mysql>showvariableslike'autocommit';+----------------+-------+|变量名|值|+----------------+------+|自动提交|ON|+----------------+--------+1rowinset(0.00sec)在自动提交模式下,如果没有启动事务显式启动一个事务,那么每条sql语句都会作为一个事务执行操作提交。可以通过以下方式关闭自动提交;需要注意的是,autocommit参数是特定于连接的,在一个连接中修改的参数不会影响其他连接。mysql>setautocommit=0;QueryOK,0rowsaffected(0.00sec)mysql>showvariableslike'autocommit';+----------------+--------+|变量名|值|+----------------+------+|自动提交|OFF|+---------------+--------+1rowinset(0.00sec)如果关闭自动提交,所有sql语句都在一个事务中,直到提交orrollback执行完,事务结束,又一个事务。特殊操作在MySQL中,有一些特殊的命令。如果在事务中执行了这些命令,commit事务会立即执行;比如DDL语句(createtable/droptable/alter/table),locktables语句等等。但是,常用的选择、插入、更新和删除命令不会强制提交事务。事务的特点:ACID原子性(Atomicity)将原子性定义为一个事务是一个不可分割的工作单元,其中的操作要么完成要么不完成;如果事务中的SQL语句执行失败,执行的语句也必须回滚,数据库恢复到事务前的状态。简单来说就是$\color{red}{Eitherallfailorallsucceed}$实现原理在讲解原子性原理之前,先介绍下MySQL的事务日志。MySQL的日志有很多种,例如二进制日志、错误日志、查询日志、慢查询日志等。此外,InnoDB存储引擎还提供了两种事务日志:重做日志(redolog)和撤销日志(回滚日志)。其中,redolog用于保证事务的持久化;undolog是事务原子性和隔离性的基础。我们来谈谈撤消日志。实现原子性的关键是在事务回滚时能够撤销所有成功执行的SQL语句。InnoDB实现回滚,依赖于undolog:当一个事务修改数据库时,InnoDB会产生相应的undolog;如果事务执行失败或者调用rollback,导致事务回滚,可以利用undolog中的信息将数据回滚到修改前的状态。undolog是逻辑日志,记录了SQL执行的相关信息。当发生回滚时,InnoDB会根据undolog的内容做与之前相反的工作:对于每一次insert,回滚时执行delete;对于每一次删除,回滚时都会执行插入;滚动时,将执行相反的更新以将数据改回。以update操作为例:事务执行update时,生成的undolog中会包含被修改行的主键(从而知道哪些行被修改了)、哪些列被修改了、这些列修改前后的值。滚动时可以使用此信息将数据恢复到更新前的状态。从上图可以看出,数据的变化伴随着回滚日志的产生:(1)产生了修改前数据(zhangsan,1000)的回滚日志(2)修改前的数据(zhangsan,0)生成根据以上过程,可以得出以下结论:每次数据更改(插入/更新/删除)操作都伴随着undo日志的生成,回滚日志必须先于数据持久化到磁盘。所谓回滚就是根据回滚日志做反向操作。比如delete的逆操作是insert,insert的逆操作是delete,update的逆操作是update。回滚过程如tips:undolog也可以这样理解。当删除一条记录时,会在undolog中记录一条对应的insert记录。当插入一条记录时,undolog中会记录一条对应的删除记录。当一个记录被更新时,它记录一个对应于对面的更新记录。Tips:逻辑日志和物理日志的区别看日志的时候,是针对一行记录的,也就是逻辑日志。如果是数据页,就是物理日志。持久性定义了事务一旦被提交,它所做的修改将永久保存在数据库中,即使系统崩溃修改的数据也不会丢失。实现原理:重做日志(WALwriteaheadlog)先了解下MySQL的数据存储机制。MySQL的表数据是存储在磁盘上的,所以访问它的时候,必须要经过磁盘IO。但是,即使你使用SSD磁盘IO也是非常耗性能的。为此,为了提高性能,InnoDB提供了缓冲池(BufferPool)。BufferPool包含了磁盘数据页的映射,可以作为缓存使用:读取数据:先从缓冲池中读取,如果没有,再从磁盘中读取,放入缓冲池中;写入数据:先写入缓冲池,缓冲池中的数据会周期性同步到磁盘(这个过程称为flushing);上述缓冲池措施虽然带来了性能上的质的飞跃,但同时也带来了新的问题。当MySQL系统宕机断电时,数据可能会丢失!!!因为我们的数据已经提交了,但是此时在缓冲池中,还没有来得及持久化到磁盘上,所以我们迫切需要一种机制来存储提交事务的数据,以备恢复数据时使用。于是redolog就派上用场了。我们来看看重做日志是什么时候产生的。既然redolog也需要存储,而且还涉及到磁盘IO,为什么要用它呢?(1)刷脏是随机IO,因为每次修改数据的位置是随机的,但是写redolog是append操作,属于顺序IO。(2)清洗是基于数据页(Page)。MySQL默认的页面大小是16KB,一个Page上的小修改必须写在整个页面上;而redolog只包含实际需要写入的部分,大大减少了无效IO。Redolog和binlog我们知道MySQL中也有binlog(二进制日志),同样可以记录写操作,用于数据恢复,但是两者有本质区别:(1)功能不同:redolog用于崩溃恢复,保证MySQL宕机不影响持久化;binlog用于时间点恢复,保证服务器可以根据时间点恢复数据,binlog也用于主从复制。(2)层次不同:redolog由InnoDB存储引擎实现,而binlog由MySQLserver层实现(请参考文章前面对MySQL逻辑架构的介绍),同时支持InnoDB等存储引擎同一时间。(3)内容不同:重做日志是物理日志,内容以磁盘的页为单位;binlog的内容是二进制的。根据binlog_format参数的不同,它可能基于SQL语句,基于数据本身,或者两者的混合。(4)写入时机不同:binlog是在事务提交的时候写入的;redolog的写入时机比较多样:如前所述:事务提交时,会调用fsync刷新redolog;这是默认的策略,修改innodb_flush_log_at_trx_commit参数可以改变策略,但是不会保证事务的持久性。除了事务提交时,还有其他机会刷新磁盘:比如master线程每秒刷新一次redolog。隔离(Isolation)的定义不同于原子性和持久性,后者侧重于对事务本身的研究。隔离研究不同事务之间的交互。隔离是指事务内的操作与其他事务隔离,并发执行的事务之间不能相互干扰。严格隔离对应事务隔离级别中的Serializable,但出于性能考虑,在实际应用中很少使用序列化。原则隔离追求的是事务在并发情况下互不干扰。为了简单起见,我们只考虑最简单的读写操作(暂时不考虑readwithlock之类的特殊操作),那么对于隔离的讨论主要分为两个方面:(一个事务)写操作对(另一个事务的影响)写操作:锁机制保证隔离(一个事务)写操作对(另一个事务)读操作的影响:MVCC保证隔离脏读、不可重复读和幻读第一看并发,读操作可能存在三类问题:脏读:在当前事务(A)中可以读取到其他事务(B)未提交的数据(脏数据)。这种现象就是脏读。举例如下(以账户余额表为例)不可重复读:事务A中读取了两次相同的数据,两次读取的结果不同。这种现象称为不可重复读。脏读和不可重复读的区别在于前者读取其他事务未提交的数据,后者读取其他事务的已提交数据。例子如下:幻读:在事务A中,根据某个条件对数据库进行了两次查询,两次查询的结果个数不同。这种现象称为幻读。不可重复读和幻读的区别可以大致理解为:前者是指数据发生了变化,后者是指数据的行数发生了变化。例如,在实际应用中使用了如下事务隔离级别。Readuncommitted在并发的时候会带来很多问题,但是相对于其他隔离级别的性能提升非常有限,所以用的比较少。Serializable强制事务序列化,并发效率很低。只有在数据一致性要求极高,不能接受并发的情况下才会用到,所以用的比较少。因此,在大多数数据库系统中,默认的隔离级别是readcommitted(如Oracle)或repeatableread(以下简称RR)。可以通过以下两条命令分别查看隔离级别:select@@tx_isolation;+----------------+|@@tx_isolation|+----------------+|REPEATABLE-READ|+----------------+1rowinset(0.00sec)MVCCRR解决脏读,不可重复读,幻读等问题,MVCC是used:MVCC的全称是Multi-VersionConcurrencyControl,是一种多版本并发控制协议。下面的例子很好的体现了MVCC的特点:同时,不同事务读取的数据可能是不同的(即多个版本)——在T5时刻,事务A和事务C可以读取到不同版本的数据。MVCC最大的优点就是读不加锁,所以读写不冲突,并发性能好。InnoDB实现了MVCC,多个版本的数据可以共存,主要靠数据的隐藏列(也叫标记位)和undolog。数据的隐藏列包括行数据的版本号、删除时间、指向undolog的指针等;MySQL在读取数据时,可以通过隐藏列来判断是否需要回滚,找到回滚需要的undolog。这样MVCC就实现了;隐藏列的详细格式不再展开。下面结合上述问题对脏读进行解释。当事务A在T3时间节点读取zhangsan的余额时,会发现数据已经被其他事务修改,状态为未提交。此时事务A读取到最新的数据后,根据数据的undolog进行回滚操作,得到事务B修改前的数据,从而避免了脏读。不可重复读当事务A在节点T2第一次读取数据时,会记录数据的版本号(数据的版本号以行为单位记录),假设版本号为1;当事务B提交时,该行记录的版本号增加,假设版本号为2;当事务A在T5再次读取数据时,发现数据的版本号(2)大于第一次读取时记录的版本号(1),因此会根据undolog进行回滚操作,获取版本号为1时的数据,实现可重复读取。幻读InnoDB实现的RR通过next-key锁机制避免了幻读。Next-keylock是行锁的一种,相当于记录锁(recordlock)+间隙锁(gaplock);它的特点是不仅锁住记录本身(记录锁的功能),而且锁住一个范围(间隙锁的功能)。当然,我们这里说的是不加锁读取:此时的next-key锁并不是真正的加锁,只是给读取的数据加上一个标记(标记的内容包括数据的版本号,ETC。);为了准确起见,我们称它为next-key锁机制。还是用前面的例子来说明:事务A在T2节点第一次读取0