mysql第六话 - mysql事务与锁详解

发布时间:2023-07-11 16:00

有这样的一个经典问题,用户A转账给用户B,是怎样保证数据的一致性的?这时就需要用到事务了。

1.什么是事务

1.1 定义

事务是数据库管理系统执行insert、update、delete过程中的一个逻辑单位,由一个有限的数据库操作序列构成

1.2 事务的四大特性

  • 原子性 undo log
  • 隔离性 锁LCC+MVCC
  • 持久性 redo log + double write buffer 双写缓冲
  • 一致性

1.3 如何使用事务

  • 增删改语句,默认会开启事务,查询变量语句show variables like \'%autocommit%\'
  • 命令begin 、commit/rollback 事务持有的锁在事务结束时释放

2.事务隔离级别

2.1 事务并发的三大问题

1.脏读(可能会回滚)

  • 一个事务读到另一个还未提交的数据
  • A开启了事务查询id=10的数据,B开启了事务更新了id=10的数据但还未提交,A查询时能查到B已修改的数据

2.不可重复读(update/delete)

  • 一个事务读到了已提交的数据,导致两次读取数据不一致
  • A开启事务查询了id=10的数据,B开启事务更新了id=10的数据,A再次查询时能查询到已经修改的数据

3.幻读(insert)

  • 一个事务读到了另一个事务插入并且已经提交的数据,造成两次读取数量不一致
  • A开启事务查询数据10条,B开启事务插入了新的数据且已提交,A查询时能查询到新增的数据

2.2 事务的四种隔离级别

\"mysql第六话

  • Read Uncommitted(RU 未提交读) – 未解决任何问题
  • Read Committed(RC已提交读) – 解决脏读问题,只能看到已提交的事务做的修改,会出现不可重复读
  • Repeatable Read(RR可重复读) – 解决不可重复读问题
    • 在同一个事务中多次读取的结果应该是一致的,但不解决幻读问题
    • 在innoDB不可能出现幻读
  • Serializable(串行化) – 解决所有问题,强制事务的串行化执行

查看数据库隔离级别sql show variables like \'tx_isolation\'

2.3 事务隔离级别解决方案

如何在一个事务里面保证两次查询一致?
1.在读数据前对其加锁,阻止其他事务对该数据加锁(LBCC,基于锁并发),高并发情况下,很不友好
2.生成一个请求时间点的一致性快照数据,用这个快照来提供一致性读(MVCC)

3 MVCC

3.1 核心思想

建立一个快照,同一个事务无论查询多少次,都是相同的数据
一个事务能看到的数据版本:

  • 第一次查询之前已经提交事务的修改
  • 本事务的修改

一个事务不能看到的版本

  • 在第一查询之后创建的事务(事务ID大于当前事务ID)
  • 未提交的事务修改

3.2 MVCC模型

在InnoDB里面,会为每行记录新加三个字段
\"mysql第六话

  • ROW_ID 行标识,默认主键
  • TRX_ID 创建版本
  • ROLL_PTR 删除版本

接下来从一张图来分析MVCC是怎么工作的:
\"mysql第六话
MVCC是怎么实现的呢?为什么每次查询都能查到第一次查询的内容?
依赖的是Read View一致性视图

3.3 Read View一致性视图

事务开启之后的第一次查询会建立这样的一个视图

  • mid[]:列表,当前系统活跃(未提交)的事务ID
  • min_trx_id 未提交的最小的事务id
  • max_trx_id 系统分配给下一个事务的ID
  • creator_trx_id 生成ReadView事务的事务ID

3.4 Read View判断规则

1.从数据的最早版本判断(undo log)
2.数据版本的trx_id = creator_trx_id,本事务修改,可见
3.数据版本的trx_id < min_trx_id,当前事务版本在生成ReadView已经提交,可见
4.数据版本的trx_id > max_trx_id,当前事务版本是生成ReadView新开的事务的,不可见
5.数据版本的trx_id在min和max之间,看看是否还在mid[]列表中,在的话说明是未提交的,不可见,反之可见。
6.如果当前版本不可见,就找undo log链中的下一个版本。

3.5 RC和RR Read View的区别

  • Read Committed(RC已提交读) – 是事务每次查询时建立的
  • Repeatable Read(RR可重复读) – 是事务第一次查询时建立的

4.Mysql InnoDB锁

在mysql中锁有表锁和行锁。

  • Myisam:只支持表锁
  • InnoDB:支持表锁和行锁

4.1 表锁

  • 锁住整个表
lock tables xxx read;
lock tables xxx write;
unlock tables;

4.2 表锁之意向共享锁(IS)/意向排它锁(IX)

  • 不需要自己实现,由存储引擎维护
  • 一个事务在加行锁时会自动给表加上一个意向锁,如果再有事务来加表锁不会存在
-- 先加行锁
begin;
select * from student where id=1 FOR UPDATE;
-- 另一个窗口加表锁 不会成功
begin;
LOCK TABLES student WRITE;

4.3 行锁之共享锁(Share Locks)

  • 读锁,加锁后只能读不能修改。例如在两张表的数据在事务没完成之前不希望其他事务修改
begin;
select * from student where id=1 LOCK IN SHARE MODE;
commit/rollback;

4.4 行锁只排它锁(X)

  • 一个事务获取了行锁,那其他事务不能再获取该行的锁
  • delete/update/insert默认加上排它锁
  • 其他事务获取锁等待超时时间show variables like \'innodb_lock_wait_timeout\', 默认50s
-- 锁住 4
begin;
select * from student where id=1 FOR UPDATE;
commit/rollback;

4.5 间隙锁(Gap Lock)

  • 锁住临近记录的区间值,间隙锁只在RR中存在
-- 锁住区间 (4,7)
select * from t1 where id>4 and id<7 for update;
-- 假如一个表的索引记录只到10 锁住 (10,+无穷大)
select * from t1 where id>15 for update;

4.6 临键锁(Next-key Lock)

  • 范围查询,包含记录和区间,位于两个区间的,会同时锁住两个区间(左开右闭)
-- 索引记录是 1,4,7,10 会锁住(4,7],(7,10] 那么插入这个范围内的数据是会阻塞 读加锁用这种解决幻读
select * from t1 where id>4 and id<9 for update;

5. 一个事务锁住了什么?

锁的作用:为解决资源竞争而存在

  • 不使用索引的情况:表锁
  • 有主键索引:行锁
  • 有主键和唯一索引
    • 行锁,且两个字段不能分开,因为非主键索引都是二级索引,指向都是主键索引,不管锁住哪个,都等于行锁

6.死锁的发生

  • 互斥
  • 不可剥夺
  • 形成等待环路
    查看死锁日志
//需要先开启 --transaction 里面有记录死锁信息
show engine innodb status;

7.死锁的避免

  • 顺序访问
  • 数据排序
  • 申请足够级别的锁
  • 一定要带上条件,否则就是表锁
  • 大事务拆分
  • 尽量少使用范围查询

以上就是本章的全部内容了。

上一篇:mysql第五话 - mysql索引原理分析
下一篇:mysql第七话 - mysql性能优化总结

一日难再晨,岁月不饶人

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号