【MySQL 流浪之旅】 第一讲 MySQL 安装
【MySQL 流浪之旅】 第二讲 MySQL 基础操作
【MySQL 流浪之旅】 第三讲 MySQL 基本工具
【MySQL 流浪之旅】 第四讲 MySQL 逻辑备份
【MySQL 流浪之旅】 第五讲 数据库设计的三个范式
目录
系列文章目录
一、什么是锁?
二、锁的类型?
三、锁的算法?
四、锁与事务隔离级别
4.1. 隔离级别简介
4.2. Read Uncommitted(读取未提交内容)
4.3. Read Committed(读取提交内容)
4.4. Repeatable Read(可重读)
4.5. Serializable(可串行化)
五、死锁
锁是实现事务隔离性最广泛使用的技术。以 MySQL InnoDB 为例,数据库的锁有排他锁,共享锁,意向锁,自增锁,间隙锁。
按照粒度分为锁定整个表的表级锁(table-level locking)和锁定数据行的行级锁(row-level locking):
InnoDB 存储引擎同时支持行级锁(row-level locking)和表级锁(table-level locking),默认情况下采用行级锁。
InnoDB的行锁是实现在索引上的,而不是锁在物理行记录上。所以如果访问没有命中索引,就无法使用行锁,将要退化为表锁(共享行锁上升为共享表锁,排他行锁上升为排他表锁)。
InnoDB存储引擎实现的是行级锁
排他锁和共享锁的兼容性
意向排他锁和意向共享锁的兼容性
平时还有提到的悲观锁,乐观锁,在数据库层面上没有这个锁的概念,如果要做简单映射,悲观锁可以映射成排他锁,乐观锁是由应用层面保障的,和 DB 的锁概念无关。
所以我们说,锁的分类从不同角度来看有不同的类型:
事务的隔离级别是用来描述并发控制机制中不同事务之间隔离程度的参数。以下是几种常见的隔离级别及其特点:
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
Read Uncommitted(读取未提交内容) | 是 | 是 | 是 |
Read Committed(读取提交内容) | 否 | 是 | 是 |
Repeatable Read(可重读) | 否 | 否 | 否 |
Serializable(可串行化) | 否 | 否 | 否 |
定义:所有事务都可以看到其他未提交事务的执行结果。(本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少)读取未提交的内容也称为脏读,所以在该隔离级别下是存在脏读这种缺陷的。在该隔离级别是允许脏读。
锁的状态:Read Uncommitted这个隔离级别是不加锁的,所有的事物都能任意操作数据,数据是没有任何一致性可言,其实数据就是处于混乱的状态中。
图示
图中所示,会导致什么问题呢?
tab2在B时刻查询到了表的数据,但是如果在C时刻发生了回滚,那么tab1的数据的插入不成功的,其实这时表里是没有数据的,但是事务tab2却查询到了数据,这就导致数据不一致了。
定义:只能读取到已经提交的数据,这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 会出现所谓的不可重复读(Nonrepeatable Read)这种缺陷,因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。
锁的状态:该隔离级别下,数据的读取都是不加锁的,但是事务的写入、修改、删除是加锁的。
定义:这是MySQL的默认事务隔离级别,他确保一事务的多个会话在并发读取数据时,会看到同样的数据行。
T1 |
T2 |
T3 |
begin; |
begin; |
begin; |
root@localhost> select id,class_name,teacher_id from class_teacher; +----+--------------+------------+ | id | class_name | teacher_id | +----+--------------+------------+ | 1 | 初三一班 | 1| | 2 | 初三二班 | 1 | +----+--------------+------------+ 2 rows in set (0.00 sec) |
||
update class_teacher set class_name='初三三班' where id=1; Waiting…… |
||
root@localhost> select id,class_name,teacher_id from class_teacher; +----+--------------+------------+ | id | class_name | teacher_id | +----+--------------+------------+ | 1 | 初三一班 | 1| | 2| 初三二班 | 1 | +----+--------------+------------+ 2 rows in set (0.00 sec) |
insert into class_teacher values (null,'初三三班',1); |
|
commit; |
||
root@localhost> select id,class_name,teacher_id from class_teacher; +----+--------------+------------+ | id | class_name | teacher_id | +----+--------------+------------+ | 1 | 初三一班 | 1| | 2 | 初三二班 | 1 | | 3 | 初三三班 | 1 | +----+--------------+------------+ 3 rows in set (0.00 sec) |
从上图可以看出,传统的Repeatable Read(可重读),为了保证可重读,读取出来的数据行是加锁的,所以这些行在commit(锁释放)前是不能修改的,但是他不会对新插入的行加锁,所以T3是可以插入数据的,并且再次查询的时候就会看到新插入的的行,这种现象就是幻读。
定义:这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁(每次读都需要获得表级共享锁)。在这个级别,可能导致大量的超时现象和锁竞争,读写相互都会阻塞。因此,会导致大量的超时现象。
锁的状态:MySQL从MVCC并发控制退化为基于锁的并发控制。不区别快照读与当前读,所有的读操作均为当前读,读加读锁 (S锁),写加写锁 (X锁)。Serializable隔离级别下,读写冲突,因此并发度急剧下降,在MySQL/InnoDB下不建议使用。
死锁是指两个或两个以上的事务在执行过程中,因争夺锁资源而造成的一种互相等待的现象。
show engine innodb status查看死锁信息
死锁通常包括以下四个必要条件:
为了预防和解决死锁问题,可以采取以下策略: