一、数据库事务
数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。事务的使用是数据库管理系统区别于文件系统的重要特征。
1、事务的四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),简称 ACID。
1.1、原子性(Atomicity)
开启事务的所有操作,要么全部执行成功,要么全都不执行。若事务执行任一操作失败,同一事务内的已经执行成功的操作,全部回滚到开启事务前的状态。
1.2、一致性(Consistency)
事务将数据库从一种一致性状态转变为另一种一致状态。事务开始前及结束后,数据库的完整性约束没有被破坏(如唯一性约束)。
1.3、隔离性(Isolation)
每个读写事务的对象对其他事务的操作对象相互隔离,即事务提交前对其他事务不可见。
并发环境中,不同事务操作相同的数据时,每个事务都有各自的完整数据空间。
1.4、持久性(Consistency)
事务一旦提交,其结果是永久性的(宕机可恢复 -- 已持久化到磁盘,不可回滚)。
注:MySQL 使用 redo log 来保证事务的持久性。
二、事务的隔离级别
1、READ UNCOMMITTED(读未提交)
此隔离级别下,一个事务会读到其他事务未提交的事务的数据,即脏读数据。
此级别下的数据库并发性能最佳。
2、READ COMMITTED(读已提交)
此隔离级别下,一个事务可读取另一个已提交的事务的数据。
同一事务中,相同的 select 操作结果不一致,即不可重复读现象。
3、REPEATABLE READ(可重复读)
此隔离级别下,同一事务中,相同的 select 操作结果一致。
不同事务中,相同的 select 操作结果不一致,即幻读现象。MySQL 的 InnoDB 引擎可通过 next-key locks 机制来避免幻读。
此级别是MySQL 的默认隔离级别。
4、SERIALIZEABLE(序列化)
此级别下,所有事务串行执行。
MySQL数据库的 InnDB 引擎会给读操作隐式加一把共享锁,从而避免了脏读、不可重复读和幻读。
三、MySQL 中的锁
锁机制也是数据库管理系统区别文件系统的重要特征之一。
锁机制在数据库进行并发访问时,保证数据的完整性、一致性。
各个数据库厂商的实现方法各异。
锁兼容:多个事务可以共同获取同一行数据的锁。
锁冲突:多个事务不可共同获取同一行数据的锁。
MySQL 的 InnoDB 引擎的锁
1、锁类型
InnoDB 实现类两种类型的行级锁:
共享锁(S锁):允许事务读取一行数据。示例:select * from tableName where ... lock in share mode;
独占锁(X锁):允许事务删除或更新一行数据。示例:select * from tableName where ... for update;
InnoDB 为实现多粒度锁机制,InnoDB 有两种内部使用的意向锁,由其自动添加,都是表级锁:
意向共享锁(IS):事务即将给表中的各个行设置共享锁,事务给数据行加 S 锁前必须获得该表的 IS 锁。
意向排他锁(IX):事务即将给表中的各个行设置排他锁,事务给数据行加 X 锁前必须获得该表的 IX 锁。
意向锁的主要目的是为了使行锁和表锁共存。行级锁与表级意向锁的兼容性如下图:
锁类型 | X | IX | S | IS |
X | 冲突 | 冲突 | 冲突 | 冲突 |
IX | 冲突 | 兼容 | 冲突 | 兼容 |
S | 冲突 | 冲突 | 兼容 | 兼容 |
IS | 冲突 | 兼容 | 兼容 | 兼容 |
四、行锁的算法
InnoDB 存储引擎使用三种行锁算法来满足相关事务隔离级别的要求。
1、Gap Locks
该锁锁定一个范围,但不锁记录本身。可通过修改隔离级别为 READ COMMITTED 或 配置 innodb_locks_unsafe_for_binlog 参数为 ON。
2、Record Locks:索引记录上的锁。
若表中没有定义索引,InnoDB 或默认为该表创建一个隐藏的聚簇索引,并使用该索引锁定记录。
3、Next-key Locks
该锁是 Record Locks 和 Gap Locks 的组合,即锁定一个范围并且锁定该记录本身。
InnoDB 使用 Next-key Locks 解决幻读问题。
注意,若索引由唯一性,InnoDB 自动将 Next-key Locks 降级为 Record Locks。
示例:索引由1,3,5 三个值,则该索引的锁定区间为(-∞,1],(1,3]、(3,5],(5,+∞)。
五、死锁
1、定义
两个或两个以上的进程/线程 在执行过程中,由于竞争资源或彼此通信而造成的一种阻塞现象,若无外力作用,将无法推进下去。此时系统处于死锁状态或系统产生了死锁,这些永远在相互等待的进程/线程 成为死锁进程/线程。
2、解决
InnoDB 引擎采用的是 wait-for graph 等待图的方法来自动检测死锁,若发现死锁会自动回滚一个事务。
3、优化建议
a. 合理设计索引,让 InnoDB 在索引键上加锁的时候尽可能准确,尽可能地缩小锁定的范围,避免造成不必要的锁定而影响其他 Query 的执行。
b. 尽可能减少基于范围的数据检索过滤条件,避免因为间隙锁带来的负面影响而锁定了不该锁定的记录。
c. 尽量控制事务的大小,减少锁定的资源量和锁定时间的长度。
d. 在业务环境允许的情况下,尽量使用较低级别的事务隔离,以减少 MySQL 因为实现事务隔离级别锁带来的附加成本。