MySQL锁
锁用于保证数据并发访问的一致性、有效性。
分类方式 | 介绍 |
性能 | 乐观锁,悲观锁 |
对数据库操作的类型 | 读锁,写锁 读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响 写锁(排它锁):当前写操作没有完成前,它会阻断其他写锁和读锁 |
对数据操作的粒度 | 表锁,行锁 |
表锁
每次操作锁住整张表。开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低;一般不使用表锁;
MyISAM引擎使用表锁。
MyISAM引擎在执行查询(select)语句前,会自动给涉及的所有表加读锁,在执行增删改操作前,会自动给涉及的表加写锁。
- 对MyISAM表的读操作(加读锁),不会阻寒其他进程对同一表的读请求,但会阻赛对同一表的写请求。只有当读锁释放后,才会执行其它进程的写操作。
- 对MylSAM表的写操作(加写锁),会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其它进程的读写操作
总结:读锁会阻塞写,但是不会阻塞读。而写锁则会把读和写都阻塞。
行锁
InnoDB引擎使用行锁。
每次操作锁住一行数据。开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度最高。
行锁还有一个重要特点:支持事务!
事务ACID属性
事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性。
原子性(Atomicity) | 事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。 |
一致(Consistent) | 在事务开始和完成时,数据都必须保持一致状态。 这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性; 事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。 |
隔离性(Isolation) | 数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。 这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。 |
持久性(Durable) | 事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。 |
MVCC机制简单了解
了解 Mysql 事务隔离级别之前,首先大概了解下 MVCC 机制,因为事务隔离级别,也是通过 MVCC 机制来实现的。
当前读 & 快照读
当前读 | 读取的是记录的最新版本,会对读取的记录加锁,是悲观锁的实现。 |
快照度 | 不加锁的 select 操作; 基于多版本并发控制 (MVCC); 可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本; |
MVCC简单介绍
MVCC,全称 Multi-Version Concurrency Control
,即多版本并发控制。用来解决读-写冲突,支持高并发。
多版本并发控制:维持一个数据的多个版本,使得读写操作没有冲突
MVCC 的实现原理主要是依赖记录中的 隐式字段,undu日志 ,Read View 来实现的。
1. 隐式字段
每行记录除了我们自定义的字段外,还有数据库隐式定义的 创建事务ID(也包含更新)、删除事务ID 字段。
创建事务ID:最近修改(insert/update)这条记录的事务ID
回滚指针:指向这条记录的上一个版本(undo日志中)
2. undo日志
undo日志用来回滚行记录到某个版本。
3. Read View
Read View 就是事务进行快照读操作的时候生产的读视图 (Read View)。
主要是用来做可见性判断,当某个事务执行快照读的时候,对该记录创建一个 Read View 读视图,用来决定当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo日志里面的某个版本的数据。
事务中快照读的结果非常依赖该事务首次出现快照读的地方,即某个事务中首次出现快照读的地方有决定该事务后续快照读结果的能力。
事务的并发问题
并发事务处理会带来几个问题,主要有脏读,不可重复读,幻读,以及更新丢失。
脏读(Dirty Reads)
事务B读取到了事务A已经修改但尚未提交的数据,还在这个数据基础上做了操作。此时,如果A事务回滚,B读取的数据无效,不符合一致性要求。
不可重复读(Non-Repeatable Reads)
一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读”。
事务A读取到了事务B已经提交的修改数据,不符合隔离性。
幻读(Phantom Reads)
一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。
事务A读取到了事务B提交的新增数据,不符合隔离性。
更新丢失(Lost Update)
当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题。最后的更新覆盖了由其他事务所做的更新。
事务隔离级别
脏读、幻读、不可重复读属于数据库一致性问题,Mysql设置了四个隔离级别,来解决这几个问题。至于更新丢失,可以通过在表上增加一个版本号,每次更新的时候根据版本号更新来解决。
- 读未提交(Read uncommited):所有事务都可以看到其他未提交事务的执行结果
- 读已提交(Read commited):一个事务只能看见已经提交事务所做的改变
- 可重复读(Repeatable read):同一事务的多个实例在并发读取数据时,会看到同样的数据行
- 可串行化(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞
隔离级别 | 脏读 | 不可重复读 | 幻读 |
读未提交 Read uncommited | 可能 | 可能 | 可能 |
读已提交 Read commited | 不可能 | 可能 | 可能 |
可重复读(默认) Repeatable read | 不可能 | 不可能 | 可能 |
可串行化 Serializable | 不可能 | 不可能 | 不可能 |
数据库的事务隔离越严格,并发情况越少,但付出的代价也就越大。因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的。
不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读"和“幻读”并不敏感,可能更关心数据并发访问的能力。所以,要根据实际情况选择数据库的隔离级别。Mysql 默认的隔离级别是:Repeatable read
serializable 隔离级别会锁表,不会出现幻读的情况,但是这种隔离级别并发性极低,开发中很少会用到。
# 查看数据库的隔离级别
show variables like 'tx_isolation';# 设置数据库的隔离级别
set tx_isolation = 'REPEATABLE-READ';
可重复读(Repeatable read)实现原理
在 Read commited 隔离级别下,每个快照读都会生成并获取最新的 Read View;所以在事务中可以看到别的事务提交的更新。
在 Repeatable read 隔离级别下,同一个事务中的第一个快照读才会创建 Read View, 之后的快照读获取的都是同一个 Read View。所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个 Read View,所以对之后的修改不可见。
事务传播行为
PROPAGATION_REQUIRED | Mysql 默认使用 如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行 |
PROPAGATION_MANDATORY | 支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常 |
PROPAGATION_REQUIRES_NEW | 创建新事务,无论当前存不存在事务,都创建新事务 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作 |