Mysql Innodb事务

事务的基本要素(ACID)

  • 原子性(Atomicity):事务开始后的所有操作,要么全部做完,要么全部不做。事务执行过程中出错,会回滚到事务开始前的状态
  • 一致性(Consistency):数据开始前和结束后,数据库的完整性约束没有被破坏。比如:A向B转账,不可能A扣了钱,B却没收到
  • 隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。
  • 持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。

事务的并发问题

脏读

A事务读取B事务尚未提交的数据,此时如果B事务发生错误并执行回滚操作,那么A事务读取到的就是脏数据。

这种情况常发生于转账和取款操作中:
时间顺序 | 转账事务 | 取款事务
—|--- |—
1 | | 开始事务
2 | 开始事务 |
3 | | 查询账户余额为2000元
4 | | 取款1000元,余额被更改为1000元
5 | 查询账户余额为1000元(产生脏读) |
6 | |取款操作发生未知错误,事务回滚余额变更为2000元
7 | 转入2000元,余额变更3000元(脏读的1000+2000)|
8 | 提交事务 |
备注:按照正常逻辑,此时账户余额应该为4000元

不可重复读

事务A在执行读取操作中,需要查询两次数据,前一次查询账户余额1000元,此时事务B更改账户余额为2000并提交后,这个时候事务A查询账户余额为2000元,和之前查询的不一样,成为不可重复读
时间顺序 | 事务A | 事务B
—|--- |—
1 | 开始事务 |
2 | 第一次查询,账户余额1000 |
3 | | 开始事务
4 | |更改账户余额2000
5 | |提交事务
6 | 第二次查询,账户余额2000 |

幻读

事务A在执行读取操作,需要两次统计数据的总量,前一次读取数据总量后,此时事务B执行了新增事务的操作并提交后,这个时候事务A读取的数据总量和之前统计的不一样,就想产生了幻觉一样,平白无故多了几条数据,成为幻读
时间顺序 | 事务A | 事务B
—|--- |—
1 | 开始事务 |
2 | 第一次查询,数据总量100条 |
3 | | 开始事务
4 | |新增100条数据
5 | |提交事务
6 | 第二次查询,数据总量200条 |

不可重复读和幻读的区别

(1)、不可重复读是读取了其他事务更改的数据,针对update操作

解决:使用行级锁,锁定该行,事务A多次读取操作完成后才释放该锁,这个时候才允许其他事务更改刚才的数据

(2)、幻读是读取了其他事务新增的数据,针对insert操作

解决:使用标记锁,锁定整张表,事务A多次读取数据总量之后才释放该锁,这个时候才允许其他事务新增数据。

SQL92标准(SQL隔离级别)

Read Uncommitted(读取未提交内容)

    在该隔离级别,所有事务都可以看到其他未提交事物的执行结果。本隔离级别很少用于实际应用,因为它的性能不比其他级别好多少。读取未提交的数据,也称之为脏读

Read Committed(读取提交内容)

    这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。解决了脏读

Repeatable Read(可重读)

这是Mysql的默认事务隔离级别。解决了不可重复读

Serializable(可串行化)

这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。

隔离级别 脏读 不可重复读 幻读
Read Uncommitted(读取未提交内容)
Read Committed(读取提交内容) ×
Repeatable Read(可重读) × × √(Innodb解决了幻读)
Serializable(可串行化) × × ×

  InnoDB引擎的锁机制:Innodb支持事务,支持行锁和表锁用的比较多,Myisam不支持事务,只支持表锁。

Mysql官方文档锁的种类

This section describes lock types used by InnoDB.

Shared and Exclusive Locks//共享锁和排它锁

Intention Locks//意向锁(意向共享锁和意向排它锁)

Record Locks//记录锁

Gap Locks//间隙锁

Next-Key Locks//临界锁

Insert Intention Locks

AUTO-INC Locks

Predicate Locks for Spatial Indexes
1、 Shared and Exclusive Locks(共享锁和排它锁)

共享锁(S):允许一个事务去读一行,阻止其他事务获取相同数据集的排它锁。

排它锁(X):允许获得排它锁的事务更新数据,阻止其他事务获取相同数据集的共享锁和排它锁

手动加锁

共享锁(S):
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE。
排他锁(X)
:SELECT * FROM table_name WHERE ... FOR UPDATE。
2、Intention Locks(意向锁)

意向锁又分为意向共享锁(IS)和意向排他锁(IX)

意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。

意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

意向锁不需要用户干预

对于UPDATE、DELETE和INSERT语句,Innodb会自动给涉及的数据集加排它锁,对于普通SELECT语句,Innodb不会加任何锁。

3、Record Locks

单条索引记录加锁,Record Lock锁住的永远是索引,而非记录本身,即使该表上没有任何索引,那么innodb会在后台创建一个隐藏的聚合主键索引,那么锁住的就是这个隐藏的聚合主键索引。所以说当一条sql没有走任何索引时,那么将会在每一条聚集索引后面加X锁。

4、Gap Locks

在索引记录之间的间隙加锁,或者在某一条索引记录的之前或之后加锁,不包含改索引记录本身,Gap Lock的机制主要是是解决了幻读问题。
Repeatable Read隔离级别才有的

5、Next-key Locks

Next-key Locks是在Innodb和Repeatable Read的情况下使用的数据库锁,官网有介绍,Next-key Locks是行锁和Gap Locks的组合。

Gap Locks介绍

假如是for update级别的操作,先看看几条总结的何时加锁的规则

  • 唯一、主键索引
    • 精确等值查询:Next-key Locks就退化为Record Locks锁,不加Gap Locks锁
    • 范围查询:会锁住where条件中的响应的范围,范围中的记录以及间隙,换言之就是加上Record Locks锁和Gap Locks
  • 非唯一索引
    • 精确等值查询:Next-key Lock会加Record Locks和Gap Locks
    • 范围查询:会锁住where条件中的响应的范围,范围中的记录以及间隙,换言之就是加上Record Locks锁和Gap Locks
  • 非索引查询:全表记录Record Locks锁,全表间隙加Gap Locks锁

Gap Locks机制解决幻读演示:

Innodb+Repeatable Read

表结构 ID主键索引 NAME唯一索引 age普通索引

Gap Locks只会对阻塞insert操作,因为Gap区间不存在任何记录,除了insert操作,其他的操作等价于空操作,所以mysql不去阻塞它

CREATE TABLE `user` (
  `ID_` bigint(20) NOT NULL AUTO_INCREMENT,
  `AGE_` int(11) DEFAULT NULL,
  `HEIGTH_` int(11) DEFAULT NULL,
  PRIMARY KEY (`ID_`),
  KEY `age` (`AGE_`)
) ENGINE=InnoDB AUTO_INCREMENT=372 DEFAULT CHARSET=utf8mb4;



演示实例1(主键索引+精确等值读)
#Session1
BEGIN;

select * from user where ID_=4 for update;
select * from user where ID_=3 for update;
Session2
BEGIN;

insert user(ID_,AGE_)  VALUES(4,1)
insert user(ID_,AGE_)  VALUES(3,1)
insert user(ID_,AGE_)  VALUES(5,1)

session1中4有值,3没有值,都会加Record Locks
session2中插入ID=4、3都会阻塞,ID=5不会阻塞

演示实例2(主键索引+范围读)
#Session1
BEGIN;

select * from user where ID_<4 for update;

#Session2
BEGIN;
insert user VALUES(1,'test',1)
insert user VALUES(4,'test',1)
insert user VALUES(5,'test',1)

session1范围读ID<4的,Gap区间是(-∞,4];
session2插入ID=1、4会阻塞,但是ID=5不会阻塞。

演示实例3(非唯一索引+精确等值读)
#Session1
BEGIN;

select * from user where AGE_=4 for update;

#Session2
BEGIN;

insert user(AGE_) VALUES(1)
insert user(AGE_) VALUES(4)

insert user(AGE_) VALUES(0)
insert user(AGE_) VALUES(5)
ROLLBACK;

Session1中查询age=4,区间是[1,4]
sesison2中age=0、5不会阻塞,age=1、4会阻塞

演示实例4(非唯一索引+范围读)
#Session1
BEGIN;

select * from user where AGE_>7 for update;
#Session2
BEGIN;
insert user(AGE_) VALUES('test',6)

insert user(AGE_) VALUES('test',7)
insert user(AGE_) VALUES('test',8)

Session1中查询age>7;区间是[7,∞+]
Session2中插入age=6成功,age=7、8阻塞

演示实例5(非索引条件读)
#Session1
BEGIN;
select * from user where HEIGTH_=4 for update;

select * from user where HEIGTH_>4 for update;
#Session2
BEGIN;

insert user(HEIGTH_) VALUES(1)
insert user(HEIGTH_) VALUES(4)

insert user(HEIGTH_) VALUES(7)
ROLLBACK;

session1中不管是精确等值读还是范围读,session2中插入任何值都会阻塞

  从上面五个演示可以看出来,innodb搜索引擎中,通过索引查询锁的是行,非索引查询使用的锁定的整张表

回头可以看脏读、不可重复度、幻读。可以通过加锁的方式解决

隔离级别 脏读 不可重复读 幻读 解决锁
Read Uncommitted(读取未提交内容)
Read Committed(读取提交内容) × 写事务加入X锁
Repeatable Read(可重读) × × √(Innodb解决了幻读) 读事务加入S锁、Innodb通过Next-key Locks解决幻读
Serializable(可串行化) × × ×

Mysql中innodb引擎中通过加锁和MVCC(多版本并发控制)的方式来控制并发

你可能感兴趣的:(SQL)