Mysql事务特性
事务概念
事务可由一条sql或者一组sql组成。事务是访问并更新数据库中各种数据项的一个程序执行单元。
事务会把数据库从一种一致状态转换为另一种一致状态。在数据提交工作时,可以确保要么所有修改都已经保存了,要么所有修改都不保存。
事务需要满足ACID特性(不同厂商、mysql不同的存储引擎并非严格遵守ACID)。
ACID特性
原子性(atomicity)
事务是不可分割的工作单位,事务中的多个操作是一个整体,要么都做,要么都不做
一致性(consistency)
一致性指事务将数据库从一种一致状态转变为下一种一致状态。事务开始之前和结束之后,数据库的完整性约束并没有被破坏。如果事务中某个动作失败了,系统可以自动撤销事务并返回事务开始之前的状态。
隔离性(isolation)
多个事务之间彼此隔离,通过锁实现。一个事务对数据的操作在提交前是对其他事务隐藏的,隔离的。
持久性(durability)
事务一旦提交,其结果就是永久性的。就是发生宕机等故障,数据库也能将提交的数据恢复。mysql通过redo log实现。
原子性由redo log(重做日志)实现;
一致性由redo log和undo log(回滚段)实现;
持久性由redo log实现;
隔离性由锁实现。
事务隔离级别
大部分数据库都没有提供真正的隔离性,隔离性可以保证正确性但却无法保证性能。
RU(Read Uncommitted,读未提交)
一个事务可以读到其他事务已修改但尚未提交的数据,脏读。安全性最低,并发度(无锁)最高。
RC(Read Committed,读已提交)
一个事务中只可以读到已提交的数据,过程中如果数据被其他事务修改并提交,则当前事务再次读时,读到的是已修改后的数据,即不可重复读。
RR(Repeatable Read,可重复读)
一个事务只读取当前事务开始时提交的数据,过程中如果数据被其他事务修改并提交,则无影响;事务开始时不存在的数据,被其他事务提交后,当前事务可以读到,即幻读(不同数据库实现不同,mysql在RR隔离级别下使用next-key锁解决了幻读问题)。
Serializable(序列化/串行)
事务串行执行,安全性最高,并发度最低。
概念
脏读
脏数据指未提交的数据
脏读是当前事务读到了其他事务中未提交的数据,即读到了脏数据
不可重复读(侧重update)
在一个事务中对同一数据的两次/多次读取,读到的数据不一样,即不可重复读
幻读(侧重insert)
在一个事务中已经检查过不存在的记录,再次查询时却已存在
隔离性的实现-锁
行锁、间隙所、Next-key锁、Previous-key锁、页锁、意向锁、表锁、MDL(元数据)锁
行锁
顾名思义,在数据记录上加的锁,分为X锁和S锁。
间隙锁
锁住一段区间,range,不包括当前记录。
行锁+间隙锁=Next-key锁/Previous-key锁
包括当前记录
意向锁
行级锁分为X(写/独占)锁、S(读/共享)锁
一致性锁定读(加锁,根据情况,阻塞其他加锁请求)
通过加锁的方式
select ... for update; 加X锁
select ... lock in share mode; 加S锁
一致性非锁定读(不加锁,不阻塞)
mvcc(多版本并发控制,通过undo log实现)
死锁
死锁是指两个或者两个以上事务在执行过程中,因争夺锁资源而造成的一种互相等待的现象
解决方案:超时、锁等待图
演示
准备
测试表建表sql
CREATE TABLE t_a (
id INT (10) NOT NULL,
content VARCHAR (10) NOT NULL,
PRIMARY KEY (id)
) ENGINE = INNODB;
开启3个mysql终端,2个用来测试(以下统一使用t1,t2名称代替),1一个用来查看锁信息(以下统一使用info3名称代替)
修改事务隔离级别命令
SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE]
隔离级别
RU隔离级别下脏读
设置事务隔离级别为RU
t1中开启事务,向t_a表中插入一条数据,不要提交
t2中开启事务,读t_a表的数据,读到了脏数据(未提交的数据)
如果t2中以此数据作为执行后续逻辑的判断条件,则可能会导致业务错误或者数据混乱。
t1中rollback
t2中再次查询,已经查不到了
RC隔离级别下无脏读
设置事务隔离级别为RC
t1中开启事务并向t_a表中插入一条记录,但是不提交
t2中开启事务,查询t_a表数据,发现不会查询出未提交的数据
提交t1中的事务
t2中再次查询,可以查询到已提交的数据,说明无脏读问题
RC隔离级别下不可重复读问题
先向t_a表中插入一条记录
t1中开启事务,并查询这条记录
t2中开启事务修改这条记录,并提交
t1中再次查询这条记录,发现数据不一致,也就是不可重复读
RR隔离级别下可重复读
设置事务隔离级别为RR
t1中开启事务,并查询t_a表中记录
t2中开启事务,修改表中记录,并提交
t1中再次查询t_a表中的记录,发现数据无变化,即可重复读
死锁
t1开启事务,对t_a的id为1的记录加X锁
t2中开启事务,对t_a的id为2的记录加X锁
t1中申请t_a的id为2的记录的X锁,被阻塞(t2中的事务正在持有锁)
t2中申请t_a的id为1的记录的X锁,报错,提示死锁
X锁、S锁
阻塞
t1中开启事务对t_a表中id为1的记录加X锁
t2中开启事务对t_a表中id为1的记录申请加S锁,被阻塞
info3中查询锁信息
MVCC
t1中开启事务对t_a表中id为1的记录加X锁
t2中开启事务读t_a表中id为1的记录,未阻塞
实例
上图中并发情况下,两个线程开启两个事务,同时执行到if代码行,则返回的结果都是true,会造成重复插入。
一般的解决方案有:
unique key
insert into ... select...
如下面的sql,
insert into t_a
select 3, '3'
from dual
where not exists (
select 0 from t_a where content = '3'
);
会对t_a表中content=3的记录加锁,阻塞其他插入content='3'的请求,从而避免重复插入