一、事务的定义
事务是数据库管理系统执行过程中的一个逻辑单位,有一个有限的数据库操作序列构成。mysql 的存储引擎中只有InnoDB支持事务。
二、事务的四大特性
- 原子性(Atomicity):原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。(InnoDB使用 undo log实现回滚操作)
- 一致性(Consistency):事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
- 隔离性(Isolation):事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离。
- 持久性(Durability):持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。(InnoDB 使用 redolog 来实现持久性)
三、事务的问题与隔离级别
什么时候会开启事务:客户端工具查看事务是否自动提交方式:SHOW GLOBAL VARIABLES LIKE "%autocommit%";有Global 和 session 两个级别。on 表示自动开启事务和提交事务。手动开启事务方式:使用 begin;或start transcation;命令。手动结束事务的话使用 commit;或者 rollback;进行回滚。提交回滚之后事务将结束。
事务带来的问题:
如果多个线程都开启自己的事务操作数据库中数据时,如果不考虑隔离性,将会出现如下问题:
- 脏读:指一个事务读取了另外一个事务未提交的数据。
比如有两个事务,一个事务在开启事务后进行转账操作,在完成对方账户转入时,自身账户还未完成扣款操作时,两个账户信息的数据被其他事务查询到,此时称为脏读。
- 不可重复读:在一个事物内读取表中的某一行数据,多次读取结果不同。
比如 银行在查询A账户余额时,第一次查询余额200,打印了一份报告。此时他人给A账户转入100并进行提交;银行再次查询A余额发现为300,再次打印其他报告时,与之前报告中的余额显示不一样。
不可重复读和脏读的区别是:脏读是读取了其他事务未提交的数据;不可重复读是读取了其他事务中已提交的数据;
- 幻读:是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。(由插入引起)
在一个事务中,同样的查询范围,由于其他事务的插入操作,使得第一次查询和第二次查询的结果集数量不一样引起的称之为幻读。
事务的隔离级别
- Read uncommitted(读未提交):最低级别,以上情况均无法保证。
- Read committed(读已提交):一个事务只能读取其他事务已经提交事务的数据,不读取未提交事务数据。可避免脏读情况发生
- Repeatable read(可重复读):在同一个事务中,多次读取相同数据的数据结果是一样的。可避免脏读、不可重复读情况的发生。
- Serializable(串行化):可避免脏读、不可重复读、幻读情况的发生。所有的事务都是串行执行,已经不存在并发现象,所以解决了所有问题。
四、InnoDB读一致性的实现方案
- LBCC :读取数据的时候,锁定我们要操作的数据,不允许其他事务修改就可以了,这种叫做基于锁的并发控制。Lock Based Concurency Control(LBCC)。
- MVCC:在修改数据的时候,我们为修改之前的数据创建一个备份,或者快照。后面在来读取我们创建的快照即可,这种称之为多版本的并发控制Multi Version Concurrency Control(MVCC)。
MVCC的思想实际为可以查到当前事务开启前数据的信息,开启事务后,其他事务对该数据所做的任何修改删除,当前该事务是查询不到的。
在InnoDB中,InnoDB为我们每行记录都添加了两个隐藏字段。
DB_TRX_ID 6字节,记录了当前的事务编号,新开事务编号都是递增的。
DB_ROLL_PTR 7字节:用于记录数据被删除,或者被更新后成为历史记录数据。存放的也是当前的事务编号。
例如:如上表格,是我们在经过
第一次创建了两个记录(事务编号1)(创建版本号都是1,删除版本 undefined);
第二次另新开启一个查询事务(事务编号2)(版本号为2,查询结果为步骤一的两条);
第三次另新开启事务(事务编号3),新增一条记录(版本号为3的记录)
第四次另新开启事务(事务编号4) 删除id为2的记录(id为2的记录中删除版本号更新为当前事务编号4);
第五次另新开启事务(事务编号5) 对id为1 的数据进行修改name列,则更新之前记录的删除版本为5,并将当前更新的记录新增创建版本设置为5.
当后面第三四五次操作之后,每次在第二步事务中查询,数据都是之前第一步中新增的两个数据。因为他每次查询都只查询事务编号小于等于2的记录。
六、InnoDB锁的类型
共享锁(Shared Locks):我们获取了一行数据的读锁后,我们可以用来读取数据,所以也称为读锁。手工加锁:select xxxx where ... lock in share mode;
排它锁(Exclusive Locks):排它锁是用来写数据的,所以也叫作写锁。只要一个事务获取了一行数据的写锁,那么其他事务就不能在获取到这行数据的读锁和排它锁。增删改都会默认加上一个排它锁。for update 也可以进行手工添加排它锁。
意向锁:意向锁为表上的锁,分为意向共享锁,和意向排他锁。当一张表中有共享锁时,则会在加共享锁之前给表加一个意向共享锁。当表中有记录需要添加排他锁时,则会给表上添加一个意向排他锁。意向锁可以用来判断表上当前是否有锁的信息。
七、InnoDB行锁的原理以及算法
在没有索引的表中:使用排他锁会将所有记录全部锁住,因为查询没有索引,会进行全表扫描,然后吧每一行隐藏的聚集索引都会锁起来。
有主键索引的表:使用相同的id的记录加锁,冲突。操作不同id,可以加锁成功。
使用辅助唯一索引的表:通过唯一索引加锁,主键索引也会被锁住。(因为辅助索引中也存放着主键索引的值,通过主键索引然后加锁)
InnoDB的行锁,实际是通过锁住索引记录来实现的,
锁的算法:
相关概念:
有一张主键索引的表,其中有4条记录 1 4 7 10,我们称这里有4个Record;
Record 之外的区域我们成为 Gap 间隙;
间隙以及它右边的记录我们称之为临建区间。
记录锁(Record Lock):当我们对于有主键索引或者唯一索引的表进行等值查询时,精确匹配到一条记录的时候,我们使用的就是记录锁。 比如 where id = 1, 4 7 10。
间隙锁(Gap Lock): 当我们查询的记录不存在,不管我们使用的是范围查询还是等值查询,使用的都是间隙锁。如:where id>4 and id <7; where id =6;
临键锁(next-key Lock): 当我们使用了范围查询,不仅仅命中了Record记录,还包含了Gap间隙。这种情况下我们使用的就是临间锁。它是mysql中默认的行锁算法。
临间锁示例:select * from t where id>5 and id <7 for update; --锁住(4,7] 和 (7,10];
select * from t where id>8 and id <=10 for update; --锁住 (7,10] 和 (10,+无穷);
Mysql 事务隔离级别 默认 RR