目录
- 事务是什么
- ACID特性
- 原子性(A)
- 隔离性(I)
- 持久性(D)
- 一致性(C)
- 隔离级别
- 简介
- 有些什么
- READ UNCOMMITTED(读未提交)
- READ COMMITTED(读已提交)
- REPEATABLE READ(可重复读)
- SERIALIZABLE
- 实现
- MVCC
- 锁
- 锁类型
- 锁算法
- 修改事务隔离性
- 并发异常
- 读异常
- 脏读
- 不可重复读
- 幻读
- 丢失更新
- 回滚覆盖
- 提交覆盖
- 并发死锁
- 原因
- 查看死锁
- 系统表
- 开启日志
- 线上查看
- 如何避免死锁
事务是什么
- 将数据库从一种一致性状态转换为另一种一致性状态
- 可以是一条语句,也可以是一组语句
ACID特性
原子性(A)
- 事务操作要么都做(提交),要么都不做(回滚)
- 事务是访问并更新数据库各种数据项的一个程序执行单元,是不可分割的工作单位
- 通过 undolog 来实现回滚操作。undolog 是将事务每步具体操作记录在共享表中,当回滚时,回放事务具体操作的逆运算
隔离性(I)
- 事务的隔离性要求每个读写事务的对象对其他事务的操作对象能相互分离,并发事务之间不会相互影响,设定了不同程度的隔离级别,通过适度破环一致性,得以提高性能
- 通过 MVCC 和 锁来实现
- 数据库中提供粒度锁的策略,针对表(聚集索引B+树)、页(聚集索引B+树叶子节点)、行(叶子节点当中某一段记录行)三种粒度加锁
持久性(D)
- 事务提交后,事务DML操作将会持久化(写入 redolog 磁盘文件 哪一个页 页偏移值 具体数据)
- 即使发生宕机等故障,数据库也能将数据恢复。redolog 记录的是物理日志
一致性(C)
- 一致性指事务将数据库从一种一致性状态转变为下一种一致性的状态,在事务执行前后,数据库完整性约束不会被破坏
- 一个事务单元需要提交之后才会被其他事务可见。例如:一个表的姓名是唯一键,如果一个事务对姓名进行修改,但是在事务提交或事务回滚后,表中的姓名变得不唯一了,这样就破坏了一致性
- 一致性由原子性、隔离性以及持久性共同来维护的
隔离级别
简介
- ISO 和 ANIS SQL 标准制定了四种事务隔离级别的标准,各数据库厂商在正确性和性能之间做了妥协,并没有严格遵循这些标准;
- MySQL innodb默认支持的隔离级别是 REPEATABLE READ
有些什么
READ UNCOMMITTED(读未提交)
- 该级别下读不加锁,
- 写加排他锁
- 写锁在事务提交或回滚后释放锁
READ COMMITTED(读已提交)
- 从该级别后支持 MVCC ,也就是提供一致性非锁定读
- 此时读取操作读取历史快照数据
- 该隔离级别下读取历史版本的最新数据,所以读取的是已提交的数据
REPEATABLE READ(可重复读)
- 支持MVCC
- 此时读取操作读取事务开始时的版本数据
SERIALIZABLE
实现
MVCC
- 多版本并发控制,
- 读取过程中,遇到其他进、线程对该资源加写锁(X锁,又称行写锁、排他锁),则读取该资源的快照(以前存储的数据)。
锁
- 用来处理并发 DML 操作
锁类型
- 表锁
- 页锁
- 行锁(记录锁)
- 共享锁(S锁)
在 SERIALIZABLE 隔离级别下,默认帮读操作加共享锁
在 REPEATABLE READ 隔离级别下,需手动加共享锁,可解决幻读问题
在 READ COMMITTED 隔离级别下,没必要加共享锁,采用的是 MVCC
在 READ UNCOMMITTED 隔离级别下,既没有加锁也没有使用 MVCC
手动加S锁:
select * from xxx lock in share mode;
- 排他锁 (X锁)
事务删除或更新加的锁;对某一行加锁
插入的时候,会在中间再加一个插入意向锁
在4种隔离级别下,都添加了排他锁,事务提交或事务回滚后释放锁
手动加X锁:
select * from xxx for update;
- 意向共享锁(IS)
仅仅为了告知即将操作的事务:当前有事务进正在进行访问,且其中几行已经加入共享锁 - 意向排他锁(IX)
仅仅为了告知即将操作的事务:当前有事务进正在进行访问,且其中几行已经加入排他锁 - 意向锁
该表中存在锁,表明该锁正在接受某事务访问
目的:排除表层次的锁操作 - AUTO-INC Lock (AI锁)
自增锁,是一种特殊的表级锁,发生在 AUTO_INCREMENT 约束下的插入操作。
采用的一种特殊的表锁机制。
完成对自增长值插入的SQL语句后立即释放。
在大数据量的插入会影响插入性能,因为另一个事务中的插入会被阻塞。
从MySQL 5.1.22开始提供一种轻量级互斥量的自增长实现机制,该机制提高了自增长值插入的性能。
较低概率造成B+树的分裂。 - 锁的兼容性
注:其中IS、IX与S、X的冲突是当X、S为全表操作时候会产生冲突,单行操作并不会产生冲突
意向锁不会与行锁冲突!!!
锁算法
-
Record Lock
单个行记录上的锁 -
Gap Lock
间隙锁,锁定一个范围,但不包含记录本身, 全开区间
解决幻读问题
仅REPEATABLE READ级别支持间隙锁
产生死锁重要原因之一 -
Next-Key Lock (记录锁+间隙锁)
产生死锁重要原因之一
锁定一个范围,并且锁住记录本身,左开右闭区间
仅REPEATABLE READ级别支持间隙锁 -
Insert Intention Lock (插入意向锁)
插入意向锁,insert操作的时候产生。
在多事务同时写入不同数据至同一索引间隙的时候,并不需要等待其他事务完成,不会发生锁等待。
提升插入的并发性能。
假设有一个记录索引包含键值4和7,两个不同的事务分别插入5和6,每个事务都会产生一个加在4-7之间的插入意向锁,获取在插入行上的排它锁,但是不会被互相锁住,因为数据行并不冲突。
产生死锁重要原因之一 -
AUTO-INC Lock (AI锁)
比行插入方式,较低概率造成B+数的分裂,所以它的性能更高 -
锁兼容
如果有事务已经持有insert Intention key, 这个时候不会影响其他事务
如果有事务想要执行insert Intention key 但是这个时候已经有事务持有了GAP或者NEXT-KEY, 则会产生阻塞
修改事务隔离性
# set transaction isolation level read uncommitted;
# set transaction isolation level read committed;
# set transaction isolation level repeatable read;
set transaction isolation level SERIALIZABLE;
begin
select * from table;
update table_a set name='lsy';
delete from table_b;
commit
并发异常
读异常
- 一个事务已经获取了插入意向锁,对其他事务是没有任何影响的
- 一个事务想要获取插入意向锁,如果有其他事务已经加了 gap lock 或 Next-key lock 则会阻塞
脏读
事务A可以读到事务B未提交的数据
不可重复读
两次读取同一条记录,读到了不一样的结果,因为有其他事务对该数据做了修改
幻读
两次读取同一范围内的记录,结果的集合不一样
由于repeatable read中规定:读取事务开始前前的版本行数据,那么在该隔离级别下,不管该事务中对改行数据做什么操作,该数据都将不变
丢失更新
回滚覆盖
该问题已经被数据库修复,数据库将拒绝该操作,
该问题会将数据回滚为事务开始之前的数据
比如事务a读取数据后做了回滚,在这期间,事务B对该数据做了修改,结果事务a回滚时候将使得事务B的操作无效
提交覆盖
两个事务同时写一个数据,后提交的事务覆盖掉了先提交的数据
并发死锁
使用 wait-for graph的方式进行死锁检测
异常报错:deadlock found when trying to get lock
原因
- 相反方向加锁
- 隔离级别冲突
查看死锁
系统表
-- 开启标准监控
CREATE TABLE innodb_monitor (a INT) ENGINE=INNODB;
-- 关闭标准监控
DROP TABLE innodb_monitor;
-- 开启锁监控
CREATE TABLE innodb_lock_monitor (a INT) ENGINE=INNODB;
-- 关闭锁监控
DROP TABLE innodb_lock_monitor
开启日志
-- 开启标准监控
set GLOBAL innodb_status_output=ON;
-- 关闭标准监控
set GLOBAL innodb_status_output=OFF;
-- 开启锁监控
set GLOBAL innodb_status_output_locks=ON;
-- 关闭锁监控
set GLOBAL innodb_status_output_locks=OFF;
-- 将死锁信息记录在错误日志中
set GLOBAL innodb_print_all_deadlocks=ON;
线上查看
-- 查看事务
select * from information_schema.INNODB_TRX;
-- 查看锁
select * from information_schema.INNODB_LOCKS;
-- 查看锁等待
select * from information_schema.INNODB_LOCK_WAITS;
如何避免死锁
- 尽可能以相同顺序来访问索引记录和表;
- 如果能确定幻读和不可重复读对应用影响不大,考虑将隔离级别降低为RC;
- 添加合理的索引,不走索引将会为每一行记录加锁,死锁概率非常大;
- 尽量在一个事务中锁定所需要的所有资源,减小死锁概率;
- 避免大事务,将大事务分拆成多个小事务;大事务占用资源多,耗时长,冲突概率变高;
- 避免同一时间点运行多个对同一表进行读写的概率;