锁和隔离级别
乐观锁和悲观锁
悲观锁与乐观锁是两种常见的资源并发锁的设计思路。乐观锁适用于多读场景,加大了系统吞吐量。而在多写的情况,会产生大量冲突,导致执行回滚操作开销大,降低性能。一般的场景下用悲观锁比较合适。
悲观锁
总是假设最坏的情况,每次拿数据的时候都认为其他人会修改,所以每次在拿数据的时候都会上锁(比如行锁、表锁、读锁、写锁等),再执行业务操作。使用悲观锁会降低效率,增加产生死锁的机会。
Java中 synchronized
和 ReentrantLock
等独占锁就是悲观锁思想的实现。在数据库中的悲观锁需要数据库本身提供支持,通过常用的 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 默认事务隔离级别为:可重复读
隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。