数据库锁及事务整理
参考文档
MySQL的事务和隔离级别
理解事务 - MySQL 事务处理机制
《MySQL技术内幕》读书笔记
一文说尽MySQL事务及ACID特性的实现原理 --- 很棒
史上最全MySQL锁讲解:页锁、共享锁、行锁、表锁、悲观锁、乐观锁
1、数据库物理事务隔离级别
-
read uncommitted(读取未提交数据)
使用查询语句不会加锁,可能会读到未提交的行(Dirty Read);---脏读 -
read committed(可以读取其他事务提交的数据)---大多数数据库默认的隔离级别
只对记录加记录锁,而不会在记录之间加间隙锁,所以允许新的记录插入到被锁定记录的附近,所以再多次使用查询语句时,可能得到不同的结果(Non-Repeatable Read); ---不可重复读(数据内容被修改了) -
repeatable read(可重读)---MySQL默认的隔离级别
多次读取同一范围的数据会返回第一次查询的快照,不会返回不同的数据行,但是可能发生幻读(Phantom Read);---幻读(数据变多了或者少了) -
serializable(串行化)
InnoDB 隐式地将全部的查询语句加上共享锁,解决了幻读的问题;
2、锁策略
乐观锁(Optimistic Lock)和悲观锁(Pessimistic Lock)
-
乐观锁,顾名思义就是非常乐观,非常相信真善美,每次去读数据都认为其它事务没有在写数据,所以就不上锁,快乐的读取数据,而只在提交数据的时候判断其它事务是否搞过这个数据了,如果搞过就rollback。乐观锁相当于一种检测冲突的手段,可通过为记录添加版本或添加时间戳来实现。
-
悲观锁,对其它事务抱有保守的态度,每次去读数据都认为其它事务想要作祟,所以每次读数据的时候都会上锁,直到取出数据。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性,但随之而来的是各种开销。悲观锁相当于一种避免冲突的手段。
选择标准:
如果并发量不大,或数据冲突的后果不严重,则可以使用乐观锁;
而如果并发量大或数据冲突后果比较严重(对用户不友好),那么就使用悲观锁。
3、数据库锁分类
- 共享锁(S锁,Shared Lock)和排他锁(X锁,Exclusive Lock)
从读写角度分的,也叫读锁(Read Lock)和写锁(Write Lock)。
持有S锁的事务只读不可写。
如果事务A对数据D加上S锁后,其它事务只能对D加上S锁而不能加X锁。持有X锁的事务可读可写。
如果事务A对数据D加上X锁后,其它事务不能再对D加锁,直到A对D的锁解除。
- 表级锁(Table Lock)和行级锁(Row Lock)
从锁的粒度角度,主要分为表级锁(Table Lock)和行级锁(Row Lock)。
表级锁将整个表加锁,性能开销最小
用户可以同时进行读操作。当一个用户对表进行写操作时,用户可以获得一个写锁,写锁禁止其他的用户读写操作。写锁比读锁的优先级更高,即使有读操作已排在队列中,一个被申请的写锁仍可以排在所队列的前列。行级锁仅对指定的记录进行加锁
这样其它进程可以对同一个表中的其它记录进行读写操作。行级锁粒度最小,开销大,能够支持高并发,可能会出现死锁。
MySQL的MyISAM引擎使用表级锁,而InnoDB支持表级锁和行级锁,默认是行级锁。
还有BDB引擎使用页级锁,即一次锁定一组记录,并发性介于行级锁和表级锁之间。
4、Spring管理逻辑事务的方式
- 编程式事务
编程式事务就是利用手动代码编写事务相关的业务逻辑,这种方式比较复杂、啰嗦,但是更加灵活可控制(个人比较喜欢)
- 声明式事务
1.为了避免我们每次都手动写代码,利用Spring AOP的方式对每个方法代理环绕,利用xml配置避免了写代码。
5、Spring逻辑事务传播行为
Spring管理的事务是逻辑事务,而且物理事务和逻辑事务最大差别就在于事务传播行为,事务传播行为用于指定在多个事务方法间调用时,事务是如何在这些方法间传播的,Spring共支持7种传播行为
-
Required:
必须有逻辑事务,否则新建一个事务,使用PROPAGATION_REQUIRED指定,表示如果当前存在一个逻辑事务,则加入该逻辑事务,否则将新建一个逻辑事务 -
RequiresNew:
创建新的逻辑事务,使用PROPAGATION_REQUIRES_NEW指定,表示每次都创建新的逻辑事务(物理事务也是不同的) -
Supports:
支持当前事务,使用PROPAGATION_SUPPORTS指定,指如果当前存在逻辑事务,就加入到该逻辑事务,如果当前没有逻辑事务,就以非事务方式执行 -
NotSupported:
不支持事务,如果当前存在事务则暂停该事务,使用PROPAGATION_NOT_SUPPORTED指定,即以非事务方式执行,如果当前存在逻辑事务,就把当前事务暂停,以非事务方式执行。 -
Mandatory:
必须有事务,否则抛出异常,使用PROPAGATION_MANDATORY指定,使用当前事务执行,如果当前没有事务,则抛出异常(IllegalTransactionStateException)。当运行在存在逻辑事务中则以当前事务运行,如果没有运行在事务中,则抛出异常 -
Never:
不支持事务,如果当前存在是事务则抛出异常,使用PROPAGATION_NEVER指定,即以非事务方式执行,如果当前存在事务,则抛出异常(IllegalTransactionStateException) -
Nested:
嵌套事务支持,使用PROPAGATION_NESTED指定,如果当前存在事务,则在嵌套事务内执行,如果当前不存在事务,则创建一个新的事务,嵌套事务使用数据库中的保存点来实现,即嵌套事务回滚不影响外部事务,但外部事务回滚将导致嵌套事务回滚。
总结,只有传播性为
PROPAGATION_REQUIRED
PROPAGATION_REQUIRES_NEW
PROPAGATION_NESTED
时候才可能开启一个新事务。