引用

https://blog.csdn.net/C_J33/article/details/79487941
https://cloud.tencent.com/developer/article/1450773
https://blog.csdn.net/qq_19865749/article/details/75353874
https://blog.csdn.net/qq_35190492/article/details/109044141

数据库锁

image.png

共享锁(读锁):其他事务可以读,但不能写。
排他锁(写锁) :其他事务不能读取,也不能写。

表锁

存在于mysql中,开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低

  • 共享锁,会阻塞其他事务修改表数据
  • 排他锁,会阻塞其他事务读和写

行锁

Innodb引擎又支持行锁,开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高

共享锁

  • 加锁与解锁:当一个事务执行select语句时,数据库系统会为这个事务分配一把共享锁,来锁定被查询的数据。在默认情况下,数据被读取后,数据库系统立即解除共享锁。例如,当一个事务执行查询“SELECT * FROM accounts”语句时,数据库系统首先锁定第一行,读取之后,解除对第一行的锁定,然后锁定第二行。这样,在一个事务读操作过程中,允许其他事务同时更新accounts表中未锁定的行。
  • 兼容性:如果数据资源上放置了共享锁,还能再放置共享锁和更新锁。
  • 并发性能:具有良好的并发性能,当数据被放置共享锁后,还可以再放置共享锁或更新锁。所以并发性能很好。

排它锁

  • 加锁与解锁:当一个事务执行insert、update或delete语句时,数据库系统会自动对SQL语句操纵的数据资源使用独占锁。如果该数据资源已经有其他锁(任何锁)存在时,就无法对其再放置独占锁了。
  • 兼容性:独占锁不能和其他锁兼容,如果数据资源上已经加了独占锁,就不能再放置其他的锁了。同样,如果数据资源上已经放置了其他锁,那么也就不能再放置独占锁了。
  • 并发性能:最差。只允许一个事务访问锁定的数据,如果其他事务也需要访问该数据,就必须等待。

更新锁

  • 加锁与解锁:当一个事务执行update语句时,数据库系统会先为事务分配一把更新锁。当读取数据完毕,执行更新操作时,会把更新锁升级为独占锁。
  • 兼容性:更新锁与共享锁是兼容的,也就是说,一个资源可以同时放置更新锁和共享锁,但是最多放置一把更新锁。这样,当多个事务更新相同的数据时,只有一个事务能获得更新锁,然后再把更新锁升级为独占锁,其他事务必须等到前一个事务结束后,才能获取得更新锁,这就避免了死锁。
  • 并发性能:允许多个事务同时读锁定的资源,但不允许其他事务修改它。

两中类型的锁共存情况

事务A锁住了表中的一行,让这一行只能读,不能写。之后,事务B申请整个表的写锁。如果事务B申请成功,那么理论上它就能修改表中的任意一行,这与A持有的行锁是冲突的。数据库需要避免这种冲突,就是说要让B的申请被阻塞,直到A释放了行锁。

意向锁的用处

  1. 如果没有意向锁,事务申请表写锁时需要判断表中的每一行是否已被行锁锁住。
  2. 在意向锁存在的情况下,事务A必须先申请表的意向共享锁,成功后再申请一行的行锁。发现表上有意向共享锁,说明表中有些行被共享行锁锁住了,因此,事务B申请表的写锁会被阻塞。
    注意:申请意向锁的动作是数据库完成的,就是说,事务A申请一行的行锁的时候,数据库会自动先开始申请表的意向锁,不需要我们程序员使用代码来申请。

更新锁的用处

更新锁在的初始化阶段用来锁定可能要被修改的资源,这可以避免使用共享锁造成的死锁现象。

  • 例如:两个事务都获取了同一数据资源的共享锁,然后都要把锁升级为独占锁,但需要等待另一个事务解除共享锁才能升级为独占锁,这就造成了死锁。
  • 一个资源可以同时放置更新锁和共享锁,但是最多放置一把更新锁。这样,当多个事务更新相同的数据时,只有一个事务能获得更新锁,然后再把更新锁升级为独占锁,其他事务必须等到前一个事务结束后,才能获取得更新锁,这就避免了死锁。

事务隔离与隔离级别的关系

事务的四大特性(ACID)

  • 原子性(atomicity): 事务的最小工作单元,要么全成功,要么全失败。
  • 一致性(consistency): 事务开始和结束后,数据库的完整性不会被破坏。
  • 隔离性(isolation): 不同事务之间互不影响,四种隔离级别为RU(读未提交)、RC(读已提交)、RR(可重复读)、SERIALIZABLE (串行化)。
  • 持久性(durability): 事务提交后,对数据的修改是永久性的,即使系统故障也不会丢失。

image.png

脏读

读写均不使用锁,数据的一致性最差,也会出现许多逻辑错误。
解决:在写操作的时候加锁,使读写分离,保证读数据的时候,数据不被修改,写数据的时候,数据不被读取。从而保证写的同时不能被另个事务写和读。

事务A,B
A进行修改
B读到修改后的
A发生回滚
B返回错误结果

image.png

不可重复读

事务A,B
A进行读
B进行修改
A再次读
A两次读取数据不一致

image.png

幻读

事务A,B
A获取行总数
B进行添加/删除
A再次获取行总数
A两次结果不一致

image.png

不可重复读和幻读到底有什么区别呢?

  1. 不可重复读是读取了其他事务更改的数据,针对update操作
    解决:使用行级锁,锁定该行,事务A多次读取操作完成后才释放该锁,这个时候才允许其他事务更改刚才的数据。
  2. 幻读是读取了其他事务新增的数据,针对insert和delete操作
    解决:使用表级锁,锁定整张表,事务A多次读取数据总量之后才释放该锁,这个时候才允许其他事务新增数据。
  • Repeatable Read(可重读)也是MySql的默认事务隔离级别,意思是读的时候需要加锁并且保持

各个数据库默认隔离级别

大多数数据库的默认级别是Read committed(读已提交),比如Sql Server , Oracle
Mysql(InnoDB)的默认隔离级别是Repeatable read(可重读)

MVCC(多版本并发控制)

MVCC 不能完全解决幻读(一定程度上可以)
快照读不加锁
当前读加锁

使用版本来控制并发情况下的数据问题,在B事务开始修改账户且事务未提交时,当A事务需要读取账户余额时,此时会读取到B事务修改操作之前的账户余额的副本数据,但是如果A事务需要修改账户余额数据就必须要等待B事务提交事务。

MVCC使得数据库读不会对数据加锁,普通的SELECT请求不会加锁,提高了数据库的并发处理能力。 借助MVCC,数据库可以实现READ COMMITTED,REPEATABLE READ等隔离级别,用户可以查看当前数据的前一个或者前几个历史版本,保证了ACID中的I特性(隔离性)。

在 MySQL 中, READ COMMITTED 和 REPEATABLE READ 隔离级别的的一个非常大的区别就是它们生成 ReadView 的时机不同。在 READ COMMITTED 中每次查询都会生成一个实时的 ReadView,做到保证每次提交后的数据是处于当前的可见状态。而 REPEATABLE READ 中,在当前事务第一次查询时生成当前的 ReadView,并且当前的 ReadView 会一直沿用到当前事务提交,以此来保证可重复读(REPEATABLE READ)。

Q.E.D.


在线