一.事务:ACID
如转账业务:
A – B 1000
A -1000
B +1000
二.事务并发引起的问题
- ·脏读
- 不可重复读
- 幻读
隔离级别:
-
读未提交 (脏读,不可重复读,幻读)这个级别对数据是没有加锁的,就是最低的级别的
-
读已提交
就是事务A只能读取到事务B提交后的数据,如果B没提交,A读取到的是之前的数据,而不是B修改后的数据。可以去除脏读(没提交的数据是读不到的)了 (不可重复读,幻读)
不可重复读:
如A读到数据 a=100,此时B修改a=120提交了(未提交A读到的还是a=100),A再查询就变成120了,这样就是不可重复读了。就是A还在同一个事务中对a的两次读取到的数据不一致了。 -
可重复读(mysql默认的)
**就是A还在同一个事务中对a的两次读取到的数据一致。**无论其他事务是否对a进行了修改是否提交了,都不影响到A事务。(幻读),但是Mysql不一样,有Next-Key锁保证不出现幻读。Next-Key = X锁+间隙锁 -
串行化
若A先开始事务,那么其他事务可以进行查的操作,但是不可以进行update,insert和delete操作。执行会被锁住。
innodb锁的原理解析
1.共享锁
select....from table where...lock in share mode
- S锁,读锁(A获得了a的共享锁,则B就不能对a进行修改,但可以进行查询,除非A事务提交了)
2.排他锁
select…for update
- X锁,写锁(A获得了a排他锁,其他事务不能对a进行加任何锁)
注意:如果我们是根据id=1对数据进行修改的话,那mysql只会对id=1这一行进行加锁(行级锁),但是如果我们是根据其他字段定位而对数据进行修改的话,mysql会对整个表进行加锁(表锁)。除非我们把这个字段定义为唯一索引。
innodb行锁,锁的是什么? 注意:行锁就是给索引上的索引项加锁,也就是说,我们只有是通过索引对数据进行检索的时候,innobd才会对数据加行级锁,否则就会使用表锁。因为没有索引嘛,那innodb就只能全表扫描了,这时候就只能加表锁了。
其实就是优化锁之间的性能的。
3.意向共享锁
- IS,事务要先获取到一个数据的共享锁之前,要先获取到意向共享锁
4.意向排他锁
- IX,事务要先获取到一个数据的排他锁之前,要先获取到意向排他锁
5.自增锁
就是针对主键id来说的,就是我们插入数据时,就算没有提交(回滚了),这些id也会丢失掉。
我们进行范围查询才会用到
6.临键锁
如果我们是采用> < between这种范围查询的话,就是为了解决幻读的问题的。(左开右闭)就是区间加锁
这个结果是有的,就是有匹配记录,那么在对应的范围内加上临键锁,其他事务就不能对这范围内的进行操作了。
7.间隙锁
没有匹配记录,临键锁会降级为间隙锁(快照读)
8.记录锁
精准匹配的,范围匹配的,范围匹配没结果的,有各种锁
- 脏读就是用排他锁解决
- 不可重复读 共享锁
- 幻读是通过间隙锁或临键锁
mysql是通过MVCC+Next-key lock解决幻读
MVCC
使用锁控制并发时,只要是写数据的任务没有完成,数据就不可以被其他的任务获取,就连读数据的select操作也会阻塞,这对并发度要求较大的环境有很大的影响,为了解决这个问题引出了数据多版本。
数据多版本实现的原理是:
1,写任务发生时,首先复制一份旧数据,以版本号区分
2,写任务操作新克隆的数据,直至提交
3,并发读的任务可以继续从旧数据(快照)读取数据,不至于堵塞
排它锁 是 串行执行
共享锁 是 读读并发
数据多版本 是 读写并发
总结
我们知道 Mysql有四个特性:ACID,A就是原子性,C就是一致性,I就是隔离性,D就是持久性。
那对于持久性Mysql是通过redo log来实现的,redo log就是重做日志,而undo log就是回滚日志,主要是用来保证原子性的,而对于隔离性的话,则是通过锁机制和MVCC来保证的,隔离的话就保证了最终数据的一致性了。
首先数据库锁的实现方式有两种嘛
1.悲观锁(共享锁,排他锁,意向共享锁,意向排他锁,临键锁,间隙锁,记录锁)
我们在进行写操作的时候,默认都是加排他锁的,像update,select,delete操作
2.乐观锁(其实就是不加锁,用MVCC来实现乐观锁)
按锁的粒度又分为行锁和表锁和间隙锁
行锁就是如果我们通过索引来检索数据的话,那它才会对索引上的索引项进行加锁,这个锁就是行锁
而如果我们只是对通过普通字段进行检索的话,那就会对其加上表锁,因为不是精确匹配嘛。
间隙锁就是两者之间的折中了,就是对一个范围进行加锁,间隙锁主要是用来解决幻读的。
通过加锁的方式可以保证数据的一致性,但是对一同一条数据,我们只能进行并发读,不能读写并发,所以这个时候就引入了乐观锁这个机制,也就是MVCC
MVCC实现的原理其实就是在进行写操作的时候,数据库会先复制一份旧的数据,然后它是按版号来进行区分,因为在数据库中的每一张表其实是冗余了两个字段,一个字段是数据创建的版本号,一个是数据过期的版本号,每一个事务都有对应的一个事务id这样,写操作就只操作新克隆出来的数据,直至事务提交,而其他事务的并发读的数据就是旧数据,所以MVCC就支持读写并发了。
然后谈一下事务的持久性吧,
因为事务的持久性是透过redo log来实现的嘛,首先我们知道,内存中的数据刷新到磁盘的时间其实是随机性的嘛,那这样的话效率就很低了,于是数据库就就在两者之间加入了一个redo log,就是我们的数据先写在redo log上,然后再讲redo log刷新到磁盘上。 假如我们的数据库宕机了,那么重启时,redo log中的数据就会重新刷新到磁盘上,这样就保证了持久性。redo的作用是为了保障已提交事务的ACID特性,也就是保证了数据的D(Durability)持久性。
再聊一下事务的原子性。
事务的原子性是通过undo log来实现的,数据库修改数据未提交的时,他会讲修改数据前的旧数据存到undo log上,当事务回滚或出事时,会从日志获取旧数据,避免未提交数据对数据库的影响。undo的作用是为了保障未提交的事务不会对数据库的ACID产生影响。也就是保证了操作的A(Atomicity)原子性。
还有一个需要注意的地方就是Innodb所有的普通select都是快照读,快照读不加锁,这也是innodb支持高并发的原因之一。