MySQL事务和锁详解
Posted 醉酒的小男人
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL事务和锁详解相关的知识,希望对你有一定的参考价值。
事务
什么是事务
事务是逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败
事务的四大特性
原子性(atomicity):强调事务的不可分割.
一致性(consistency):事务的执行的前后数据的完整性保持一致.
隔离性(isolation):一个事务执行的过程中,不应该受到其他事务的干扰.
持久性(durability):事务一旦结束,数据就持久到数据库
原子性:在InnoDB里面是通过undo log实现的,它记录了数据修改之前的值(逻辑日志),一旦发生异常,就可以用undo log来实现回滚操作。
持久性:通过redo log和double write双写缓冲来实现的,我们操作数据的时候,会先写到内存的buffer pool里面,同时记录redo log,如果在刷盘之前出现异常,在重启后就可以 读取redo log的内容,写入到磁盘,保证数据的持久性。当然,恢复成功的前提是数据页本身没有被破坏,是完整的,这个通过双写缓冲(double write)保证。
原子性、隔离性、持久性,最后都是为了实现一致性。
事务并发会带来什么问题
脏读:一个事务读到了另一个事务未提交的数据。比如:事务A查询where id =1的name的值等于AAA,事务B更新了where id=1的name值等于BBB,但是事务B未提交事务,事务A再次查询的事务,查询到了name的值是BBB。事务A都是在未关闭事务的情况下查询2次。
不可重复读:一个事务读到了另一个事务已经提交的update的数据导致多次查询结果不一致。比如:事务A查询where id =1的name的值等于AAA,事务B更新了where id=1的name值等于BBB,但是事务B提交事务,事务A再次查询的事务,查询到了name的值是BBB。事务A都是在未关闭事务的情况下查询2次。
虚幻读:一个事务读到了另一个事务已经提交的insert的数据导致多次查询结果不一致。比如事务A查询where id >1的数量是一条,事务B又添加了一条数据id等于3,但是事务B提交了事务,事务A再次查询id > 1事务,查询到的数量变成了2条。事务A都是在未关闭事务的情况下查询2次。
其实无论是脏读,不可重复读,还是幻读,它们都是数据库的读一致性的问题,都是在一个事务里面前后两次读取出现了不一致的情况。读一致性的问题,必须通过数据库的隔离级别机制来解决。
隔离级别
两大实现方案
解决的是保证在一个事务里面前后两次读取数据结果一致,实现事务隔离,也就是读一致性问题
LBCC
第一种,我既然要保证前后两次读取数据一致,那么我读取数据的时候,锁定我要操作的数据,不允许其他的事务修改就行了。这种方案我们叫做基于锁的并发控制LockBasedConcurrencyControl(LBCC)
MVCC
所以我们还有另一种解决方案,如果要让一个事务前后两次读取的数据保持一致,那么我们可以在修改数据的时候给它建立一个备份或者叫快照,后面再来读取这个快照就行了。这种方案我们叫做多版本的并发控制MultiVersionConcurrencyControl(MVCC)
MVCC原则
一个事务能看到的数据版本:
1.在本事务第一次查询之前已经提交的事务的修改
一个事务不能看到的数据版本:
1.在本事务第一次查询之后创建的事务(事务ID比我的事务ID大)
2.活跃的(未提交)的事务的修改
MVCC效果
我可以查询到在我这个事务开始之前已经存在的数据,即使它在后面被修改或者删除了。而在我这个事务之后新增的数据,我是查不到的
MVCC原理
InnoDB的事务都是有编号的,而且会不断递增。InnoDB为每行记录都实现了两个隐藏字段:
DB_TRX_ID,6个字节:事务ID,数据被插入或修改的时候,记录当前的事务ID
DB_ROLL_PTR,7个字节:回滚指针(删除版本号),数据被删除或记录为旧数据的时候,记录当前事务ID,没有删除或修改时是空
id | name | DB_TRX_ID | DB_ROLL_PTR |
1 | BBB | 01 | NULL |
在InnoDB中,一条数据的旧版本是存放在undolog里面的,如果一条数据被修改了多次,就会存在一个undolog链,DB_ROLL_PTR就是指向这个undolog链的。
可见性视图
m_ids{} | min_trx_id | max_trx_id | creator_trx_id |
列表,当前系统活跃的事务id,未提交的事务 | m_ids的最小值 | 系统分配的下一个事务的id | 生成readView事务的事务id |
怎么判断事务可见性:
1.从数据的最早版本undolog开始判断.
1.从数据的最早版本undolog开始判断.
2.数据版本的trx_id=creator_trx_id,本事务修改可以访问.
3.数据版本的trx_id<min_trx_id(未提交是事务id的最小值),说明了这个版本在生成readView已经提交,可以访问.
4.数据版本的trx_id>max_trx_id(下一个事务ID),说明了这个版本在生成readView之后才开启的事务建立的,不能访问.
5.数据版本的trx_id在min_trx_id和max_trx_id之间,看看是否在m_ids里面,如果在,不能访问。如果不在,可以访问。
6.如果当前版本不可见,就找undolog链中的下一个版本.
RR中Read View是事务第一次查询的时候建立的。RC的Read View是事务每次查询的时候建立的。
mysql InnoDB的锁
表锁和行锁的区别
锁定粒度:表锁>行锁
加锁效率:表锁>行锁
冲突概率:表锁>行锁
并发性能:表锁<行锁
锁的类型
行锁
共享锁:共享锁会堵塞其它事务的修改,所以可以用在不允许其它事务修改数据的情况.(select...lock in share mode手工加锁)
排它锁:只要一个事务获取了一行数据的排它锁,其它事务就不能在获取这一行数据的共享锁和排它锁。(FOR UPDATE)
插入意向锁:是一个特殊的间隙锁。间隙锁不允许查询数据,但是插入意向锁允许多个事务同时插入数据到同一个范围。比如(5,8),一个事务查询6,一个事务插入7,不会发生锁等待
表锁
自增锁:是一种特殊的表锁,用来防止自增字段重复,数据插入以后就会释放,不需要等到事务提交才释放。
意向锁:1.意向锁是由数据库维护的。也就是说,当我们给一行数据加上共享锁之前,数据库会自动在这张表上面加一个意向共享锁。当我们给一行数据加上排它锁之前,数据库会自动在这张表上面加一个意向排它锁。2.反过来说:如果一张表上面至少有一个意向共享锁,说明有其它事务给表中的某些数据行加上了共享锁。如果一张表上面至少有一个意向排它锁,说明有其它事务给表中的某些数据行加上了排它锁。3.正常来说,如果我们要给一个表加上表锁的话,首先我们需要判断是否有其它的事务锁定的表中的某些行,如果有的话,肯定不能加上表锁。那么这个时候我们就需要扫描整张表来判断是否有其它的事务锁定了某些行,也就是判断是否可以加表锁,如果数据量非常大,加锁的效率是很低的。如果有意向锁的话,我们只需要判断这张表上面有没有意向锁,如果有,就直接返回失败。如果没有,就可以加锁成功。所以InnoDB的表锁,可以理解成一个标识。就像火车里的卫生间的等,主要是为了提高加锁的效率。
行锁的原理
行锁锁定的索引。
行锁的算法
记录锁:当我们对唯一性索引(包括唯一索引和主键索引)使用等值查询,精准匹配到一条记录的时候,这个时候使用的是记录锁。
间隙锁:当我们的查询记录不存在,没有命中任何记录,无论是等值查询还是范围查询的时候,它使用的是间隙锁。是一个左开右开的区别。
临界锁:当我们使用返回查询,不仅仅命中了记录锁,还包括间隙锁,这种情况使用的是临建锁,是MySQL默认的行锁算法,相当于记录锁加上间隙锁。它可以分别退化为记录锁和间隙锁。临界锁锁定的是最后一个key的下一个左开右闭的区间,是为了解决幻读的问题。
隔离级别原理
未提交读:不加锁
已提交读:普通的select使用的是快照读,底层使用的是MVCC实现。加锁的select使用的是记录锁,因为没有间隙锁。除了两种特殊情况会使用间隙锁封锁区别,分别是外键约束检查和重复键检查。所以已提交读会出现幻读的问题。
可重复读:普通的select使用的是快照读,底层使用的是MVCC实现。加锁的select(不管是共享锁还是排它锁)以及更新操作update,delete等语句使用的是当前读,底层使用记录锁、间隙锁、临键锁。
串行化:所有的select都会隐式的转化为共享锁,会和update、delete互斥。
以上是关于MySQL事务和锁详解的主要内容,如果未能解决你的问题,请参考以下文章