MYSQL之事务

128 阅读6分钟

事务

事务可谓是db中非常重要的一个知识点了,接下来我们的目标就是弄懂什么是事务,怎么使用事务,以及事务与锁之间的关联是怎样的

说明:本文的分析主要是以mysql的innordb存储引擎为标准

1. 定义

事务就是一组原子性的sql,或者说一个独立的工作单元。

事务就是说,要么mysql引擎会全部执行这一组sql语句,要么全部都不执行(比如其中一条语句失败的话)。

2. ACID特性

a. A:atomiciy 原子性

一个事务必须保证其中的操作要么全部执行,要么全部回滚,不可能存在只执行了一部分这种情况出现。

b. C:consistency一致性

数据必须保证从一种一致性的状态转换为另一种一致性状态。

c. I:isolation 隔离性

在一个事务未执行完毕时,通常会保证其他Session 无法看到这个事务的执行结果

d. D:durability 持久性

事务一旦commit,则数据就会保存下来,即使提交完之后系统崩溃,数据也不会丢失

3. 隔离级别

前面在分析锁的sql时,就提到了隔离级别,通常有四种: RU, RC, RR, Serializable

在说明这个之前,先了解几个概念

a. 基本概念

  • 脏读:读取到一个事务未提交的数据,因为这个事务最终无法保证一定执行成功,那么读取到的数据就无法保证一定准确
  • 不可重复读:简单来说就是在一个事务中读取的数据可能产生变化,同样的sql,在一个事务中执行多次,可能得到不同的结果
  • 幻读:会话T1事务中执行一次查询,然后会话T2新插入一行记录,这行记录恰好可以满足T1所使用的查询的条件。然后T1又使用相同 的查询再次对表进行检索,但是此时却看到了事务T2刚才插入的新行
  • 加锁读:select * from table ... 的执行是否加了读锁 (这个可以参考上面的sql加锁分析)

b. RU: Read Uncommited 未提交读

事务中的修改,即使没有提交,对其他会话也是可见的,即表示可能出现脏读,一般数据库都不采用这种方案

c. RC: Read Commited 提交读

这个隔离级别保证了一个事务如果没有完全成功(commit执行完),事务中的操作对其他会话是不可见的,避免了脏读的可能

但是可能出现不可重复度的情况,举例说明:

  • 会话T1, 执行查询 select * from where id=1,第一次返回一个结果
  • 会话T2, 执行修改 update table set updated=xxx where id=1 并提交
  • 会话T1,再次执行查询 select * from where id=1,这次返回的结果中update字段就和前面的不一样了

实际的生产环境中,这个级别用的比较多,特意查了下公司的db隔离级别就是这个

一个RC级别的演示过程:

  • 会话1,开启事务,查询
  • 会话2,开启事务,更新DB,提交事务
  • 会话1,再次查询,提交事务
  • 从下面的实际演示结果可以知道,会话1,同一个sql,两次执行的结果不同

相关的sql代码如下:

-- 设置会话隔离级别
set session transaction ioslation read commited;

-- 查看当前会话隔离级别
select @@tx_isolation;

-- 会话1的操作
start transaction;
select * from newuser where userId=1;


-- 会话2开始操作
start transaction;
select * from newuser where userId=1;
update newuser set updated=1521786092 where userId=1;
select * from newuser where userId=1;
commit;


-- 再次进入会话1,同样执行上次的sql,对比两次输出结果
select * from newuser where userId=1;

-- 注意观察,会话1,前后两次这个sql的输出结果,特别是updated字段
-- 正常情况会如上面的demo图,会发生改变


-- 关闭会话
commit;

-- 再次查询
select * from newuser where userId=1;
复制代码

d. RR: Repeatable Read 可重复度

一个事务中多次执行统一读SQL,返回结果一样。 这个隔离级别解决了脏读的问题,幻读问题

实例演示解决脏读的过程(将上面的过程同样来一次)

  • 发现不管会话1同一个sql,返回的结果都是相同的

e. Serializable 可串行化

最强的隔离级别,通过给事务中每次读取的行加锁,写加写锁,保证不产生幻读问题,但是会导致大量超时以及锁争用问题。

f. 常用命令

  • 查看当前会话隔离级别: select @@tx_isolation

  • 查看系统当前隔离级别: select @@global.tx_isolation

  • 设置当前会话隔离级别: set session transaction isolation level read committed;

  • 设置系统当前隔离级别: set global transaction isolation level read committed;

  • 命令行,

    • 开始事务: start transactioin;
    • 提交: commit;

4. 使用姿势

前面演示事务隔离级别的时候,给出的实例就演示了事务的使用姿势,一般作为三步骤:

  • 开始事务 start transaction;
  • 执行你的业务sql
  • 提交事务 commit;

我们现在演示以下一个事务中,读锁、写锁对另一个事务的影响

a. 读锁的影响

我们采用mysql默认的RR级别进行测试,userId为主键

-- 会话1
start transaction;
select * from newuser where userId=1 lock in share mode;

-- 转入会话2
start transaction;
select * from newuser where userId=1; -- 会输出
select * from newuser where userId=1 lock in share mode; -- 会输出
update newuser set updated=1521787137 where userId=1; -- 会挂起


-- 转入会话1
-- 提交, 此时观察会话2的写是否完成
commit;

-- 转入会话2
commit;
复制代码

f4fadc397f454d2996d77e48c831c2fb~tplv-k3u1fbpfcp-zoom-1.image)

b. 写锁的影响

-- 会话1
start transaction;
select * from newuser where userId=1 for update;

-- 转入会话2
start transaction;
select * from newuser where userId=1; -- 会输出
select * from newuser where userId=1 lock in share mode; -- 会挂住

-- update newuser set updated=1521787137 where userId=1; -- 会挂住

-- 转入会话1
-- 提交, 此时观察会话2的写是否完成
commit;

-- 转入会话2
commit;
复制代码

c. 小结

  • 读锁,会阻塞其他请求写锁的sql执行
  • 写锁,会阻塞其他读锁和写锁的sql执行
  • 事务只有在提交之后,才会释放锁
  • 额外注意,上面事务在提交之后才会释放锁,因此如果两个事务循环依赖锁时,可能发生死锁