MySQL的事务的ACID特性,事务并发问题,以及事务的隔离级别。关于锁机制方面,简单聊一聊锁思想,常见的锁,以及如何加锁。
1 事务
1.1 事务的ACID特性
- A 原子性:Atomic,事务内的所有
操作
,要么全部成功,要么全部失败。 - C 一致性:Consistent,事务前后,涉及的
数据
是从一致的状态到另一个一致的状态,也就不能出现脏写。 - I 隔离性:Isolation,事务与事务之间相互隔离,互不干扰。
- D 持久性:D,事务提交成功,数据便持久化保持到磁盘。
1.2 事务并发的问题
- 脏写:多个事务并发对同一数据进行操作,且都操作成功了。结果是最后一个事务的操作覆盖了前面的。
- 脏读:一个事务读取到了另一个事务未提交的数据。
- 不可重复读:一个事务多次对同一数据进行读取,读到的数据会变化。
- 幻读:一个事务读取到了另一个事务已提交的新增数据。
1.3 事务的隔离级别
查看当前数据库的隔离级别
show variables like 'tx_isolation';
- 读未提交:存在
脏读
、不可重复读
和幻读
等并发问题。set tx_isolation='read-uncommitted';
- 读已提交:存在
不可重复读
、幻读
等并发问题。set tx_isolation='read-committed';
- 可重复读:存在
幻读
的并发问题。set tx_isolation='repeatable-read';
- 可串行化:没有并发问题,因为读写操作都会加锁,并发度最差。
set tx_isolation='serializable';
由于MySQL的四个隔离级别的写操作
都会隐式加锁
,确保了同一时刻只有一个事务在对涉及的数据进行写操作,所以没有脏写
的问题。
2 锁机制
2.1 乐观锁 & 悲观锁
乐观锁与悲观锁就是两种思想,不是真正的锁。
乐观锁是认为业务中读操作
远多于写操作
,并发写操作的可能性低,操作时不加锁。
悲观锁则与乐观锁相反,操作时直接加锁。
2.2 读锁 & 写锁
读锁: 也叫共享锁
(shared lock)。
读锁是共享的,是相互不阻塞的。多个事务在同一时刻可以同时读取同一个资源,互不干扰。但会阻塞写操作。
写锁: 也叫排它锁
(exclusive lock)。
写锁是排他的,加锁后会阻塞其它事务获取该资源的写锁和读锁。
2.3 锁的粒度
2.3.1 表锁
锁粒度最大,锁的是整个表。InnoDB
和MyISAM
等存储引擎都支持。
优点:开销小,上锁快。
缺点:并发度低。
锁操作:
- 显式锁操作 be like:
# 加读锁 lock table TableName read # 加写锁 lock table TableName write # 查看被上锁的 show open tables; # 释放锁 unlock tables;
- 隐式锁操作:一般是DDL语句,比如:给表加字段、加索引等等。在执行前会给整个表加锁,执行完毕会自动解锁。
阻塞问题:
- 如果给表上的是写锁,其它事务的
读操作
、写操作
、获取读锁
和写锁
都会被阻塞。 - 如果给表上的是读锁,其它事务可以获取
读锁
和进行读操作
,但写操作
和获取写锁
会被阻塞。
2.3.2 行锁
锁粒度最小,锁的是对应的行记录。MyISAM
不支持,InnoDB
支持。
优点:锁粒度小,并发度高。
缺点:开销大,上锁慢。
锁操作:
-
显式锁操作 be like:
# 加写锁(for update) select * from account where id = 1 for update; # 加读锁(lock in share mode) select * from account where id = 1 lock in share mode; # 解锁:事务提交/回滚
-
隐式锁操作: 一般是
update
或delete
语句等写操作,InnoDB存储引擎都会自动对要操作的行记录加锁。
阻塞问题:
- 如果给表记录上的是写锁,其它事务可以进行
读操作
,但写操作
、获取读锁
和写锁
会被阻塞。 - 如果给表记录上的是读锁,其它事务可以获取
读锁
和进行读操作
,但写操作
和获取写锁
会被阻塞。
2.3.3 间隙锁
间隙锁(Gap Lock),锁的不是记录,而是已经存在的记录之间的间隙。只在可重复读
的隔离级别下生效。
优点:
- 可以用来防止
幻读
问题; - 锁粒度适中。
缺点:在可重复读
隔离级别下才会生效。
为什么说锁的是间隙呢?
先确认当前事务隔离级别是REPEATABLE-READ
可重复读。
show variables like 'tx_isolation';
现有student表如下(主键ID列中3-10之间有空隙):
事务A,去执行加间隙锁操作:
# 开启事务
begin;
# 对id列的3-10之间加`间隙锁`
select * from student where id > 3 and id < 10 for update;
此时,事务B去插入一条id为8的记录,会发现被阻塞了,直到事务A提交/回滚事务:
insert into student (id, name, age, address) values (8, 'Li', 20, '香港');
如果,把间隙锁的范围放大会怎么样? 比如:之前锁的是(3, 10)这个区间,现在我们把10也包含进去===>(3, 10]。
你会发现:id大于10的(比如id=12)也会被阻塞了,也就说10到正无穷都被锁了。
2.3.4 临键锁
临键锁(Next-key Locks),是行锁
与间隙锁
的组合。
注意:本文归作者所有,未经作者允许,不得转载