遇见时光

Keep Looking , Don't Settle
Stay Hungry , Stay Foolish

DatabaseNotes-Ch10

Ch10. Update Transaction

1. 事务及其特性:

事务:是访问和更新数据库的程序执行单元;事务中可能包含一个或多个SQL语句,这些语句要么都执行,要么都不执行。


事务的四大特性:ACID

  • 原子性(Atomicity)

    原子性是指一个事务是一个不可分割的工作单位,其中的操作要么都做,要么都不做;如果事务中一个SQL语句执行失败,则已执行的语句也必须回滚,数据库退回到事务前的状态。

    注:MySQL 使用 undo log 来保证事务的原子性。


  • 持久性(Durability)

    持久性是指事务一旦提交,它对数据库的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

    注:MySQL 使用 redo log 来保证事务的持久性。


  • 隔离性(Isolation)

    与原子性、持久性侧重于研究事务本身不同,隔高性研究的是不同事务之间的相互影响。隔离性是指,事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

    注:MySQL 通过锁机制来保证事务的隔离性。


  • 一致性(Consistency)

    一致性是指事务执行结束后,数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态;

    数据库的完整性约束包括但不限于:实体完整性(如行的主键存在且唯一)、列完整性(如字段的类型、大小、长度要符合要求)、外键约束、用户自定义完整性(如转账前后,两个账户余额的和应该不变)。


2. 事务操作:

典型的事务操作:

1
2
3
start transaction;
…… # 一条或多条sql语句
commit;

其中start transaction标识事务开始,commit提交事务,将执行结果写入到数据库。如果SQL语句执行出现问题,会调用rollback,回滚所有已经执行成功的SQL语句。当然,也可以在事务中直接使用rollback语句进行回滚。


特殊操作:

在MySQL中,存在一些特殊的命令,如果在事务中执行了这些命令,会马上强制执行commit提交事务;如DDL语句(create table/drop table/alter/table)、lock tables语句等等。

不过,常用的select、insert、update和delete命令,都不会强制提交事务。


3. 事务的实现

1. undo log(回滚日志)

  • InnoDB实现回滚,靠的是undo log:

    当事务对数据库进行修改时,InnoDB会生成对应的undo log;如果事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。


  • undo log属于逻辑日志,它记录的是SQL执行相关的信息:

    当发生回滚时,InnoDB会根据undo log的内容做与之前相反的工作:对于每个insert,回滚时会执行delete;对于每个delete,回滚时会执行insert;对于每个update,回滚时会执行一个相反的update,把数据改回去。

zxk


2. redo log(重做日志)

  • 更新流程

    zxk

    • 执行update操作。
    • 先将原始数据从磁盘读取到内存,修改内存中的数据。
    • 生成一条重做日志写入redo log buffer,记录数据被修改后的值。
    • 当事务提交时,需要将redo log buffer中的内容刷新到redo log file。
    • 事务提交后,也会将内存中修改数据的值写入磁盘。

  • 恢复机制

    于是,redo log被引入来解决这个问题:当数据修改时,除了修改缓冲区中的数据,还会在redo log记录这次操作;当事务提交时,会调用fsync接口对redo log进行刷盘。如果MySQL宕机,重启时可以读取redo log中的数据,对数据库进行恢复。


    redo log实现事务的原子性和持久性:

    • 原子性,是redo log记录了事务期间操作的物理日志,事务提交之前,并没有写入磁盘,保存在内存里,如果事务失败,数据库磁盘不会有影响,回滚掉事务内存部分即可。
    • 持久性,redo log 会在事务提交时将日志存储到磁盘redo log file,保证日志的持久性。

4. 事务的隔离级别

zxk


1. READ-UNCOMMITTED

READ-UNCOMMITTED 中文叫未提交读,即一个事务读到了另一个未提交事务修改过的数据,整个过程如下图:

zxk

如上图,SessionA和SessionB分别开启一个事务,SessionB中的事务先将id为1的记录的name列更新为’lisi’,然后SessionA中的事务再去查询这条id为1的记录,那么在未提交读的隔离级别下,查询结果由’zhangsan’变成了’lisi’,也就是说某个事务读到了另一个未提交事务修改过的记录。但是如果SessionB中的事务稍后进行了回滚,那么SessionA中的事务相当于读到了一个不存在的数据,这种现象也称为脏读


2. READ COMMITTED

READ COMMITTED 中文叫已提交读,或者叫不可重复读。即一个事务能读到另一个已经提交事务修改后的数据,如果其他事务均对该数据进行修改并提交,该事务也能查询到最新值。如下图:

zxk

在SessionA中先后两次读取同一个数据,两次读取的结果不一样;在第4步 SessionB 修改后,如果未提交,SessionA是读不到,但SessionB一旦提交后,SessionA即可读到SessionB修改的内容。


3. REPEATABLE READ

REPEATABLE READ 中文叫可重复读,即事务能读到另一个已经提交的事务修改过的数据,但是第一次读过某条记录后,即使后面其他事务修改了该记录的值并且提交,该事务之后再读该条记录时,读到的仍是第一次读到的值,而不是每次都读到不同的数据。如下图:

zxk

InnoDB默认是这种隔离级别,SessionB无论怎么修改id=1的值,SessionA读到依然是自己开启事务第一次读到的内容;但是可能会出现幻读现象


4. SERIALIZABLE

SERIALIZABLE 叫串行化, 上面三种隔离级别可以进行 读-读 或者 读-写、写-读三种并发操作,而SERIALIZABLE不允许读-写,写-读的并发操作。 如下图:

zxk

SessionB 对 id=1 进行修改的时候,SessionA 读取id=1则需要等待 SessionB 提交事务。可以理解SessionB在更新的时候加了X锁。


  • 幻读:

    在事务A中按照某个条件先后两次查询数据库,两次查询结果的条数不同,这种现象称为幻读。不可重复读与幻读的区别可以通俗的理解为:前者是数据变了,后者是数据的行数变了。

    zxk


5. 隔离级别与读问题的关系

zxk

在实际应用中,读未提交在并发时会导致很多问题,而性能相对于其他隔离级别提高却很有限,因此使用较少。可串行化强制事务串行,并发效率很低,只有当对数据一致性要求极高且可以接受没有并发时使用,因此使用也较少。因此在大多数数据库系统中,默认的隔离级别是读已提交(如Oracle)或可重复读。


5. 锁机制

锁机制使得在对数据库进行并发访问时,可以保障数据的完整性和一致性。

基本原理:

事务在修改数据之前,需要先获得相应的锁;获得锁之后,事务便可以修改数据;该事务操作期间,这部分数据是锁定的,其他事务如果需要修改数据,需要等待当前事务提交或回滚后释放锁。


1. 锁的类型

  • 共享锁(也称为 S 锁):允许事务读取一行数据。

    共享锁又称为读锁。若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁为止。

    1
    2
    -- 手动加S锁
    select * from tableName where … lock in share mode;

  • 排他锁(也称为 X 锁):允许事务删除或更新一行数据。

    排他锁又称为写锁。若事务T对数据对象A加上X锁,则只允许T读取和修改A,其他任何事务都不能再对A加任何类型的锁,直到T释放A上的锁为止。

    1
    2
    -- 手动加 X 锁
    select * from tableName where … for update;

    zxk

S 锁和 S 锁是兼容的,X 锁和其它锁都不兼容,举个例子,事务 T1 获取了一个行 r1 的 S 锁,另外事务 T2 可以立即获得行 r1 的 S 锁,此时 T1 和 T2 共同获得行 r1 的 S 锁,此种情况称为锁兼容,但是另外一个事务 T2 此时如果想获得行 r1 的 X 锁,则必须等待 T1 对行 r 锁的释放,此种情况也成为锁冲突。


  • 更新锁:更新锁的初始化阶段用来锁定可能要被修改的资源,这可以避免使用共享锁造成的死锁现象。

  • 意向锁:设计目的是为了在一个事务中揭示下一步将要被请求的锁的类型,使得行锁和表锁共存。

    当一个事务在需要获取资源的锁定时,如果该资源已经被排他锁占用,则数据库会自动给该事务申请一个该表的意向锁。如果自己需要一个共享锁定,就申请一个意向共享锁。如果需要的是某行(或者某些行)的排他锁定,则申请一个意向排他锁。


2. 锁的优化

锁如果利用不好,会给业务造成大量的卡顿现象,在了解了锁相关的一些知识点后,我们可以有意识的去避免锁带来的一些问题。

  • 合理设计索引,让 InnoDB 在索引键上面加锁的时候尽可能准确,尽可能的缩小锁定范围,避免造成不必要的锁定而影响其他 Query 的执行。
  • 尽可能减少基于范围的数据检索过滤条件,避免因为间隙锁带来的负面影响而锁定了不该锁定的记录。
  • 尽量控制事务的大小,减少锁定的资源量和锁定时间长度
  • 在业务环境允许的情况下,尽量使用较低级别的事务隔离,以减少 MySQL 因为实现事务隔离级别所带来的附加成本。
大爷,赏点?