mysql事务与锁3

文章目录

  • 事务
    • 原子性
    • 隔离性
    • 持久性
    • 一致性
  • 事务并发带来的问题(隔离性)
  • 事务隔离级别
  • 两种方案解决读一致性问题
    • LBCC
    • MVCC
      • 这个数据快照怎么实现的?
  • Mysql InnoDB锁的基本类型
    • 锁的类型
      • 共享锁
      • 排它锁
      • 意向锁
    • 行锁的原理
      • 锁的算法
      • 记录锁
      • 间隙锁
      • 临建锁
    • 隔离级别的实现总结
  • 死锁
    • 死锁的发生和检测
      • 查看死锁日志
      • 死锁的避免

事务

mysql只有InnoDB支持事务,这也是它默认存储引擎的原因。
事务的ACID十大特性。

原子性

简单说一条或多条sql语句,要么全部成功,要么全部失败。
原子性,在InnoDB中通过undo log来实现的。一旦中途发生异常,可以用undo log实现回滚。

隔离性

数据库层面有4种隔离级别来保证。

持久性

持久性,指的是事务一旦提交成功,就会持久化到磁盘。不会因为宕机、重启而丢数据。
持久性是通过redo log和双写缓冲来保证的。

一致性

一致性指的是数据库的完整性没有被破坏,需要注意的是原子性、隔离性、持久性最后都是为了实现一致性。

事务并发带来的问题(隔离性)

脏读: A事务读取了B事务未commit的数据
mysql事务与锁3_第1张图片
不可重复度: 同一个事务里,2次读取的内容不一致。即A事务
读取了B事务已经提交的数据
mysql事务与锁3_第2张图片
幻读: A事务读取了B事务插入的数据

mysql事务与锁3_第3张图片
不可重复度和幻读的区别在哪里:
修改和删除造成的读不一致,叫做不可重复度
插入造成的读不一致,叫做幻读
相同点在于它们都是事务提交造成的读不一致

事务隔离级别

READ UNCOMMITTED(未提交读)

一个事务还没提交时,它做的变更就能被别的事务看到。 事务可以读取未提交的数据,这也被称为脏读(Dirty Read)。

READ COMMITTED(读提交)

一个事务提交之后,它做的变更才会被其他事务看到。
一个事务从开启事务到提交事务之前,对其他事务都是不可见的,因此在同一个事务中的两次相同查询结果可能不一样。故这种隔离级别有时候也叫不可重复读(NONREPEATABLE READ)。

REPEATABLE READ(可重复读)

一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。 当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。

这个级别下,没有定义解决幻读的问题。
但是mysql的InnoDB在可重复读的级别下,解决了幻读问题。

SERIALIZABLE(可串行化)

顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。
当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

InnoDB默认使用RR(可重复读)默认作为事务隔离级别。

两种方案解决读一致性问题

如果要解决事务中读一致的问题,保证一个事务中前后两次读取数据结果一致。实现事务隔离,我们该怎么做?

LBCC

第一种就是加锁,锁定我要操作的数据,不允许其它事务修改就行了。这种方案叫做基于锁的并发控制。
如果用锁来实现事务隔离,一个事务读取的时候不允许其他事务修改,那么意味着不支持并发操作,而大多数应用都是读多写少。这样会极大的影响操作数据的效率。

MVCC

让一个事务前后两次读取数据保持一致,那么我们可以在修改数据之前给它建立一个备份或者快照,后来再读取这个快照就行了。这种方案叫做多版本并发控制。(Multi Version Concurrency Control)MVCC

MVCC的效果:我可以查到我这个事务开始之前已经存在的数据,即使它被修改或删除了。而在我这个事务之后新增的数据,我是查不到的。
所以我们才把这个叫做快照,不管别的事务做任何增删改查操作,它只能看到第一次查询时看到的数据版本。

这个数据快照怎么实现的?

首先InnoDB的事务都是有编号的,而且不断递增。
InnoDB中一条数据的旧版本,是存放在undo log。因为修改多次undo log会形成一个链条,叫做undo log链。
经过一系列判断,最终实现读一致性。MVCC其实是一种乐观锁机制。

需要注意,在InnoDB中MVCC和锁是协同使用的。第一大类解决方案还是锁,锁怎么实现读一致性。

Mysql InnoDB锁的基本类型

锁的粒度:表锁和行锁。
InnoDB同时支持表锁和行锁。

锁的类型

官网把锁分为8类。

共享锁

共享锁是第一个行级别的锁。 可以用来读取数据,也叫读锁
注意不要加了读锁以后去写数据,不然可能会出现死锁。而且多个事务可以共享一把读锁。
共享锁的作用:因为共享锁会阻塞其它事务的修改,所以可以用在不允许其它事务修改数据的情况。

排它锁

第二个行级别的锁叫做Exclusive Locks(排它锁)。用来修改数据,也叫写锁。只要一个事务获取了一行数据的排它锁,其他数据都不能再获取这行记录的共享锁和排它锁。
排它锁,在做增删改的时候,会默认加上。

查询语句后面加上for update可以开启行锁。
eg:

select * from user where id = 1 for update;

意向锁

意向锁是一个表锁,但是跟行锁不冲突。
比如在加读锁的时候,系统会默认加上一个意向共享锁。别的线程再加读锁的时候,就会直到已经有人再加读锁了。
同理,在加写锁的时候,系统会默认给我们加一个意向排它锁。告诉别的事务,有事务在某条记录加了行锁。

行锁的原理

行锁的本质其实并不是去锁一条记录,而是锁住索引
InnoDB的行锁,就是通过锁住索引实现的。

锁的算法

我们插入4条数据,主键id分别是1、4、7、10。
因为我们用主键索引加锁,所以划分标准就是主键索引的值。
mysql事务与锁3_第4张图片
这些数据库的主键值,我们叫做Record,记录。那么这里我们有4个Record。
根据主键,这些Record隔开的数据不存在的区间,我们叫做Gap,间隙。它是一个左开右开的区间。
那么我们有N个Record,就会有N+1个Gap间隙。
间隙(Gap)连同他右边的记录(Record),我们叫做临建区间。他是一个左开右闭的区间。

记录锁

等值查询的时候,使用的就是记录锁。
mysql事务与锁3_第5张图片
比如where id= 1 4 7 10
我们使用不同的key去加锁,不会冲突,它只锁住Record。

间隙锁

mysql事务与锁3_第6张图片
eg:where id > 4 and id < 7,where id = 6。
当范文查询的记录不存在时,使用间隙锁。
注意:间隙锁主要阻塞插入insert。

临建锁

当使用范围查询时,不仅命中了Record记录,还包含了Gap间隙。这种情况下,使用的就是临建锁。**临建锁是Mysql里面默认的行锁算法。**相当于间隙锁加上记录锁。
mysql事务与锁3_第7张图片
临建锁为什么要锁住下一个左开右闭的区间?就是为了解决幻读问题。

隔离级别的实现总结

为什么InnoDB的RR级别能够解决幻读问题,就是用临建锁实现的。
最后总结一下4种隔离级别:
Read Uncommitted
RU隔离级别,不加锁。
Repeatable Read
RR隔离级别下,普通的select使用快照读,底层使用MVCC来实现。
Read Commited
RC隔离级别,普通select都是快照读,使用MVCC实现。
加锁的select都使用记录锁,因为没有Gap Lock。
所以RC会出现幻读。

死锁

锁什么时候释放?
事务结束(commit,rollback);客户端连接断开。

如果一个事务一直未释放锁,其它事务被阻塞多久?
mysql会有一个默认时间50秒。

死锁的发生和检测

死锁的发生需要满足一定的条件,所以发生死锁时,InnoDB会通过算法自动检测到。
但多个事务发生等待环路时,就会发生死锁。

查看死锁日志

show status命令可以查看死锁日志

死锁的避免

1、程序中应该尽量顺序来访问,避免形成等待环路。
总之不要形成等待环路。

你可能感兴趣的:(mysql,JavaWeb开发,mysql,数据库)