前言:不怕误人子弟,就怕连误都不敢!!!
一、数据库事务
简单理解就是一个操作过程,有始有终的。–>开始–结束(提交或回滚) EXP:
1.事务的开启:BEGIN
2.事务的执行:一系列操作语句
3.事务的提交或回滚:COMMIT/ROLLBACK
二、事务的特性
1.原子性 :操作原子性,要么数据全部提交,要么数据全部回滚,看重的是整个操作过程
2.一致性 :数据一致性,提交前与提交后的数据要保持一致,要么提交,要么回滚,类似于原子性,只是一致性看重的是结果
3.隔离性 :设置不同的数据库隔离界别会引发不同的数据问题(下面会详细介绍)
4.持久性 :通过日志重做的方式保证数据持久化,即一次提交,永久保存
三、事务不同隔离级别引发的不同问题
id | name |
---|---|
1 | ns |
脏读:
1.
BEGIN; ------开启事务1
update table set name = 'xw' where id = 1; ------执行更新语句
2.
BEGIN; ------开启事务2
select name from table where id = 1;------此时查出的结果为ns
COMMIT;------提交事务,返回的数据就是ns
3.COMMIT;------提交事务1,此时数据库中的值已经变成了xw
注:事务1先更新数据,但事务没有提交,此时事务2查询数据,并且直接提交事务,
此时事务1才提交事务,这种情况就会造成事务2查询的数据不准确,即为脏读。
不可重复读
1.
BEGIN;------开启事务1
select name from table where id = 1;------事务1执行查询语句,此时查询到的结果为ns
2.
BEGIN;
update table set name = 'xw' where id = 1; ------事务2执行更新语句
COMMIT;------事务2提交事务,此时数据库中的结果为xw
3.
select name from table where id = 1;------事务1
COMMIT;------此时查询的结果为xw
注:事务1先执行查询并未提交事务,事务2执行更新并提交事务,此时事务1再次查询,
查询到的结果与第一次查询的结果将不一致,此时造成了不可重复读。
幻读
1.
BEGIN;------开启事务1
select *from table;------事务1执行查询语句,此时查询到1条数据
2.
BEGIN;------开启事务2
insert into table(2,'xw');------事务2执行了insert语句
COMMIT;------事务2提交事务,此时数据库中有两条数据
3.
select * from table;------事务1执行查询,此时查询到的结果为两条数据
COMMIT;------事务1提交事务
注:事务1查询了两次,第一次结果为1条数据,第二次结果为2条数据,这种情况即为幻读。
四、四种隔离级别
Read uncommitted (读未提交):最低级别,任何情况都无法保证。
Read committed(读已提交):可避免脏读的发生。
Repeatable read (可重复读):可避免脏读、不可重复读的发生。
Serializable(串行化):可避免脏读、不可重复读、幻读的发生。注:数据库默认的隔离级别为Repeatableread(可重复读),该情况并未解决幻读的问题,为什么为默认的呢?接下来了解下数据库Innodb引擎。
五、Innodb特性
相对于其他数据库引擎,Innodb主要的两大特性:
1.锁的级别不同
Innodb实现了行锁+表锁(特殊的行锁,锁定的是表中所有的数据行)
2.对事物的支持
先简单介绍下表锁与行锁的区别
范围 | 比较 |
---|---|
锁定粒度 | 表锁>行锁 |
加锁效率 | 表锁>行锁 |
冲突概率 | 表锁>行锁 |
并发性 | 表锁<行锁 |
对于并发性而言,行锁的效率远远高于表锁。
六、Innodb加锁类型
1.共享锁(读锁)
定义:多个事务可以对同一个数据共享访问,不能修改
加锁方式:LOCK IN SHARE MODE
EXP:以person表举例
id | name |
---|---|
1 | ns |
4 | xw |
9 | xm |
1.
BEGIN; ------事务1开启事务
select * from person where id = 1 LOCK IN SHARE MODE------事务1使用共享锁获取数据
2.
BEGIN;------事务2开启事务
select * from person where id = 1 LOCK IN SHARE MODE------事务2使用共享锁获取数据
COMMIT;
注:事务1此时并未提交事务,事务2也能查询到数据
3.
BEGIN;------事务3开启事务
update person set name = 'xh' where id = 1;------事务3执行更新(增删都行)语句
COMMIT;
注:事务3在事务1并未提交事务时进行更新操作,此时事务阻塞,只有事务1提交之后,事务3才能执行通过。
2.排他锁(写锁)
定义:一个事务持有排他锁,其他事务均不能进行任何操作。 Innodb引擎默认将所有的DELETE、UPDATE、INSERT执行默认添加锁。
加锁方式:FOR UPDATE
EXP:
1.
BEGIN;------事务1开启事务
select * from person where id = 1 FOR UPDATE;------事务1执行查询
2.
BEGIN;------事务2开启事务
select * from person where id = 1 FOR UPDATE;------事务2执行查询,此时事务2会处于等待锁的状态,除非事务1提交或者回滚。
注:此种情况即为排他锁,即有一个事务持有排他锁,其他事务均不能进行操作。
那么这个锁机制是什么呢?
先看几个截图
EXP1:
1.表设计(id为主键,主键是含有索引的)
2.事务1 查询id为1的数据,事务并未提交,并且添加了排它锁
3.事务2(查询id为4的数据,并且使用排他锁进行查询的,并且有结果返回)
总结:对比两个事务的查询结果,在事务1未提交事务的前提,事务2还可以获取到数据。(先在脑子里记下这个结果)
EXP2:
1.事务1(带有排它锁通过name查询数据,name为普通字段,没有索引)
2.事务2(带有排他锁通过id查询数据)
总结: 事务1带有排他锁通过不含索引的name字段查询数据,没有提交事务,事务2带有排他锁通过id字段查询数据,需要等待锁。
对比之前的结果,发现带有排他锁通过普通字段检索数据时,如果不提交事务,其他事务都不能进行操作;带有排他锁通过索引字段检索数据时,如果不提交事务,其他事务通过索引字段查询其他数据时,仍有返回。
对比总结:
Innodb行锁的实现:通过索引项进行检索数据时,才会进行行锁,否则进行表锁。
七、行锁算法
1.临键锁
目的:为了解决幻读(为Innodb默认的行锁算法)
使用临键锁的前提:按索引检索+检索条件为范围查找+有数据命中
实现:
id | name |
---|---|
1 | ns |
4 | xw |
8 | xm |
以上面的数据表为例: Innodb引擎默认会把该表的数据分为四个区间(左开右闭)
(-∞,1],(1,4],(4,8],(8,+∞]
select * from table where id>5
锁的区间:命中记录区间+下一个区间
该查询语句Innodb引擎会使用临键锁,默认锁住(4,8],(8,+∞]这俩区间的数据。
当这两个区间数据被锁住后,其他事务执行insert语句将会一直等待,这样就能避免幻读。
2.间隙锁
使用间隙锁的条件:按索引检索+条件为范围或者等值查询+没有数据命中
锁的范围:
向上寻找最靠近记录值得数据A作为左区间,向下寻找最靠近记录值 得数据B作为右区间,(A,B)即为锁范围
目的:解决不可重复读问题
3.记录所
使用条件:按索引检索+等值匹配+有数据命中 锁的范围:锁定当前记录 目的:解决脏读
注:本篇为学习过程中的记录,如果误,请指正。
最后,希望大家都能学以致用。