初探MySQL事务

前言

事务是MySQL之类的关系型数据库的一个重要特性,是保证数据一致性的重要手段,这章我们来学习事务的基本概念,以及事务实现的相关原理。

基本概念

通常我们认为的Transaction是一个最小的不可分割的单元,通常一个事务对应一个完整的业务。在技术面试中经常被面试官问道ACID 原子性 、一致性、隔离性、持久性。

  • 原子性:指事务的最小单元,不可再分离,是一个整体。

  • 一致性:事务中的方法要么成功,要么都不成功,比如A向B转账,要不都成功,要不都失败。

  • 隔离性:指当多个事务操作数据库中的同一条或者多条记录时,对事务进行隔离开来有序执行,避免对同时对同一数据做操作。

  • 持久性:即当成功插入一条数据库记录的时候,数据库必须要保证有一条数据永久的写入到数据库磁盘中。

事务的四大特征当中,所有的操作最后都会走向磁盘,所以持久性是事务操作的目的,原子性是事务实现的基础,隔离性是实现数据安全的一种策略、手段,最终维护的就是数据的一致性,一致性是事务中最重要的。隔离性是为了保证一致性的实现的手段跟策略。

ACID四大特征中,最难理解的不是一致性,而是事务的隔离性。

按照严格的要求标准,只有同时满足ACID特性才是真正的事务。但是在各大数据厂商的实现中,真正满足ACID的事务非常少。例如MySQL NDB Cluster事务不满足持久性和隔离性。InnoDB默认事务级别是可重复读,不满足隔离性。Oracle默认的是事务隔离级别为READ COMMITED,不满足隔离性.....与其说ACID是事务满足的条件,不如说是衡量事务的四个维度。

 

事务隔离级别脏读不可重复读幻读示例
READ_UNCOMMITED 
READ_COMMITED 
REPEATABLE_READMySQL
SERIALIZABLE 

事务的实现

  • redo log

    redo log 叫做重做日志,使用来实现事务的持久性。该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log),前者是在内存中,后者在磁盘中。当事务提交之后,会把所有修改信息都会存在该日志中。

    MySQL为了提升性能不会把每次的修改都实时同步到磁盘,而是会存到Buffer Pool缓冲池里头,把这个当作缓存来使用,然后使用后台线程去做缓冲池和磁盘之间的同步。如果还没来得及同步的时候宕机了,会导致丢失部分已提交的事务修改信息!

    引入redo log 记录已成功提交事务的修改消息,并且会把redo log持久化到磁盘,系统重启自后再读取redo log 恢复更新数据。

  • 总结: redo log是用来恢复数据用于保障已提交事务的持久化特性。

     

  • undo log

    • undo log 叫做回滚日志,用于记录数据被修改前的信息。他正好跟前面所说的重做日志所记录的相反,重做日志记录数据被修改后的信息。undo log 主要记录的数据的逻辑变化,为了在发生错误的时候进行回滚之前的操作。需要之前的操作的都记录下来,然后在发生错误的时候进行回滚。

    • 总结: undo log是回滚数据用于保障未提交事务的原子性。

    • 假设有两个数值,分别为A和B,值为1和2。

      1. start transaction;

      2. 记录 A =1 到 undo log;

      3. update A =3 ;

      4. 记录 A=3 到 redo log;

      5. 记录B=2 到 undo log;

      6. update B =4 ;

      7. 记录 B=4 到 redo log;

      8. 将 redo log刷新到磁盘;

      9. commit;

      在1-8步的任意一步宕机,事务未提交,该事务就不会对磁盘上的数据做任何影响,如果8-9 之间宕机,恢复之后可以选择回滚,也可以选择事务继续,完成事务提交。因为此时redo log已经持久化。 如果在第9步,提交后宕机,那么可以在系统恢复后,根据redo log将数据刷回磁盘。

  • 所以,redo log其实保障的是事务的持久性和一致性,而undo log则保障了事务的原子性。

  • 锁&MVCC

    • 锁在上一章已经详细介绍过

      • 当有多个请求来读取表中的数据的时候不采取任何操作,但是请求里面有读的请求,又有修改请求的时候,必须有一种措施来进行并发的控制,不然有可能造成不一致

    • 为了解决上面的问题,MySQL引入了读写锁来解决这样的问题,读锁是共享的,不会造成堵塞;写锁是排他的,会一直阻塞,直到写入完成释放锁。

    • mvcc

    InnoDBmvcc,是通过在每行记录的后面保存两个隐藏的列来实现。这两个列,一个保存了行的创建时间,一个保存了行的过期时间。当然存储的并不是时间的值,而是系统的版本号。 摘抄于《高性能MySQL》。主要的实现思想是通过数据多版本来实现读写分离,从而实现不加锁做到读写并行。

    MVCCMySQL中的实现是依赖于 undo log & read view

    • undo log: undo log 中记录某行数据的多个版本的数据

    • read view: 用来判断当前版本的数据可见性

      • Read View中存储了活跃的事务ID列表(m_ids)事务ID是事务开始时分配的,是由InnoDB分配的,大小可以决定事务的先后顺 序,因此可以通过ID的大小关系来决定版本的可见性

MVCC 实现原理

MVCC读写锁的应用

 

  • MySQl在存储的每列数据隐藏了三列

    • DB_TRX_ID 6 byte, 创建这条记录/最后一次更新这条记录事务id

    • DB_ROLL_PTR 7 byte ,回滚指针,指向这条记录的上一个版本,(存储于 rollback segment里)

    • DB_ROW_ID 6 byte, 隐含的自增ID,如果数据表没有主键,InnoDB会自动以DB_ROW_ID 产生一个聚簇索引。

  • 另外每条记录的头信息里面(record header) 有一个专门的bit(delete flag)表示当前的flag是否删除。

  • 记录的历史版本是存放在rollback segment里(undo log)

    update 非主键语句的效果

    • 历史记录被复制到rollback segment中形成 undo log ,DB_TRX_IDDB_ROLL_PTR 不动

    • 新记录的DB_TRX_ID = 当前事务ID,DB_ROLL_PTR 指向老记录形成的undo log

    • 这样就能通过DB_ROLL_PTR 找到这条记录的历史版本。如果对同一条记录执行连续的update操作,新记录的undo log 会形成一个链表,遍历这个链表可以看到这条记录的变迁。

  • MySQL的一致性读

    一致性读是通过read view 来实现的。

    trx_id_t   low_limit_id; // The read should not see any transaction with trx id >= this value. In other words, this is the "high water mark".  事务ID的最大值
    trx_id_t    up_limit_id; // The read should see all trx ids which are strictly smaller (<) than this value. In other words, this is the "low water mark".  事务ID的最小值

    select操作返回结果的可见性是由以下规则决定的:

    • DB_TRX_ID < up_limit_id 此记录的最后一次修改在read view 创建之前,可见

    • DB_TRX_ID > low_limit_id 此记录的最后一次修改在read view 创建后,不可见,需要用DB_ROLL_PTR 查找undo log(此记录的上一次修改),然后根据undo log 的 DB_TRX_ID 再次计算可见性

    • up_limit_id <= DB_TRX_ID <= low_limit_id 需要判断read view是否有DB_TRX_ID

      • 如果当前read view 不包含 DB_TRX_ID 也就是改记录是在修改 read view 之前创建, 则是可见的

      • 如果当前read view 包含DB_TRX_ID 也就是该记录最后一次修改read view 还没有来得及保存,是不可见的。

    • 如果该记录的delete_flag为true,则说明这条记录被删除了,不返回,如果为false,则可以安全返回。

 

  • 隔离级别RR RC RUMVCC

    RR: read view 是在first touch read时创建,也就是执行事务中第一条select语句的瞬间,后续所有的select都复用这个read view,所以能保证每次读取的一致性。

    RC:每次读取,都会创建一个新的read view,这样就能读取到其他事务已经commit的内容。

    所以对InnoDB来说RR虽然比RC隔离级别高,但是开销反而相对较少。

    RU:相对来说实现简单,不用care read view,也不需要考虑DB_TRX_ID DB_ROLL_PTR ,直接读取最新的record就可以了。

  • 二级索引与MVCC

    在二级索引中并不包含DB_TRX_IDDB_ROLL_PTR 隐藏列,可以比较容易的实现mvcc,二级索引一般只包含一个delete_flag.

    • 如果根据二级索引来查询,因为二级索引不维护版本信息,无法判断二级索引的可见性,还是需要回到聚簇索引中来查找记录

    • 二级索引中有个MAX_TRX_ID 记录最后一次更新事务的ID

    如果当前语句关联的read_view的up_limit_id > MAX_TRX_ID,说明在创建read_view时最后一次更新二级索引的事务已经结束,也就是说二级索引里的所有记录对于当前查询都是可见的,此时可以直接根据二级索引的deleted flag来确定记录是否应该被返回。

  • Purge

    delete 和 update 都不会直接删除原有的数据delete from table where id =1假设ID是主键索引,根据 undo log 的知识,MySQL仅仅是将主键ID=1 的这一列数据的delete flag 设置为1,记录并没有被立即删除。真正的删除这行数据的时候 在purge操作完成。

    purge 用于最终完成delete和update操作,这样操作是因为InnoDB引擎支持MVCC,所以记录不能在事务提交的时候立即处理。这个时候有可能有其他的事务正在引用这行。所以存储引擎需要保持记录之前的版本。否可以删除,由purge 来判断。 purge 会根据该行记录是否有事务的引用,来执行真正删除的逻辑。

     

     

mvcc 详细的实现原理参考:

https://blog.csdn.net/joy0921/article/details/80128857

 

数据库事务的相关命令

  • 查看存储引擎 show create table表名

  • 更改存储引擎 alter table 表名 engine = new engine;

  • 回滚: rollback.

  • 声明事务开始:begin;

  • 事务提交: commit;

  • 查询自动提交功能状态:select @@autocommit 0是 off ,1 是 on;

  • 设置事务隔离级别SET SESSION TRANSACTION ISOLATION LEVEL

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值