锁和隔离级别

乐观锁和悲观锁

悲观锁与乐观锁是两种常见的资源并发锁的设计思路。乐观锁适用于多读场景,加大了系统吞吐量。而在多写的情况,会产生大量冲突,导致执行回滚操作开销大,降低性能。一般的场景下用悲观锁比较合适。

悲观锁

总是假设最坏的情况,每次拿数据的时候都认为其他人会修改,所以每次在拿数据的时候都会上锁(比如行锁、表锁、读锁、写锁等),再执行业务操作。使用悲观锁会降低效率,增加产生死锁的机会。

Java中 synchronizedReentrantLock 等独占锁就是悲观锁思想的实现。在数据库中的悲观锁需要数据库本身提供支持,通过常用的 select for update 操作来实现悲观锁,即获取到被选中的的数据行的行锁,行锁会在当前事务结束时自动释放。

不同的数据库对 select for update 的实现和支持有所区别的,例如,Oracle 支持 select for update no wait 表示拿不到锁立刻报错,而不是等待;Mysql 没有 no wait 这个选项。另外,Mysql 使用 select for update 所有扫描过的行都会被锁上,如果在 Mysql 中用悲观锁务必要确定走了索引,而不是全表扫描,否则会锁表。

乐观锁

总是假设最好的情况,每次拿数据的时候都认为其他人不会修改,所以不会在拿数据的时候上锁。而是在更新的时候进行判断,在持有数据操作期间数据有没有被其他人更新(比如判读版本号或CAS算法机制),并且在更新时获取更新行的写锁(不允许写并发),更新成功后释放锁。

如果数据已经被其他人更新过,此时认为获取锁失败,需要回滚操作。

乐观锁适用于多读的应用类型,可以提高吞吐量。

读写异常

在并发场景中有三种常见的读写异常

读写异常 现象 解释
脏读 在事务中读到了另一个未提交的事务的数据 读取到的数据是无效的
不可重复读 对于某个数据,一个事务内多次查询却返回了不同的值 与脏读的区别是,其他事务提交了数据
幻读 对于一批数据,一个事务内多次查询却返回了不同的数据集 不可重复读查询的都是同一数据,幻读针对的是一批数据整体

隔离级别

为了在不通场景下解决上面三种读写异常,定义了四种事务隔离级别

事务隔离级别 脏读 不可重复读 幻读 锁级别
读未提交(Read Uncommitted) 允许 允许 允许 不加锁
读已提交(Read Committed) 不允许 允许 允许 行锁
可重复读(Repeatable Read) 不允许 不允许 允许 Next-Key 锁,行锁+Gap锁
串行化(Serializable) 不允许 不允许 不允许 锁表

MySQL 默认事务隔离级别为:可重复读

隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。

Tags:

Add a Comment

电子邮件地址不会被公开。 必填项已用*标注