乐观锁与悲观锁属于从数据访问维度设定的两种锁的思想,经常出现在数据库相关问题中,即当数据同时被多个数据访问了,应该持有什么态度对数据进行保护
读写锁和互斥锁是同一维度的概念,都是从“读写行为”本身角度来说的,经常出现在编程相关问题中,即当读写一个变量时,其他人也要读写此变量,应该制定怎样的规则,不至于让数据乱套。
乐观锁
乐观锁: 是应用系统层面和数据的业务逻辑层次上的(实际上并没有加锁,只不过大家一直这样叫而已),利用程序处理并发, 它假定当某一个用户去读取某一个数据的时候,其他的用户不会来访问修改这个数据,但是在最后进行事务的提交的时候会进行版本的检查,以判断在该用户的操作过程中,没有其他用户修改了这个数据。开销比较小,乐观锁的实现大部分都是基于版本控制实现的。
优势:如果数据库记录始终处于悲观锁加锁状态,可以想见,如果面对几百上千个并发,那么要不断的加锁减锁,而且用户等待的时间会非常的长, 乐观锁机制避免了长事务中的数据库加锁解锁开销,大大提升了大并发量下的系统整体性能表现 所以如果系统的并发非常大的话,悲观锁定会带来非常大的性能问题,所以建议就要选择乐观锁定的方法, 而如果并发量不大,完全可以使用悲观锁定的方法。乐观锁也适合于读比较多的场景。
劣势: 但是乐观锁也存在着问题,只能在提交数据时才发现业务事务将要失败,如果系统的冲突非常的多,而且一旦冲突就要因为重新计算提交而造成较大的代价的话,乐观锁也会带来很大的问题,在某些情况下,发现失败太迟的代价会非常的大。而且乐观锁也无法解决脏读的问题
悲观锁
完全依赖于数据库锁的机制实现的,在数据库中可以使用Repeatable Read的隔离级别(可重复读)来实现悲观锁,它完全满足悲观锁的要求(加锁)。
它认为当某一用户读取某一数据的时候,其他用户也会对该数据进行访问,所以在读取的时候就对数据进行加锁, 在该用户读取数据的期间,其他任何用户都不能来修改该数据,但是其他用户是可以读取该数据的, 只有当自己读取完毕才释放锁
劣势:开销较大,而且加锁时间较长,对于并发的访问性支持不好
优势:能避免冲突的发生
选取
乐观锁和悲观所各有优缺点,在乐观锁和悲观锁之间进行选择的标准是:发生冲突的频率与严重性。
如果冲突很少,或者冲突的后果不会很严重,那么通常情况下应该选择乐观锁,因为它能得到更好的并发性,而且更容易实现。但是,如果冲突太多或者冲突的结果对于用户来说痛苦的,那么就需要使用悲观策略,它能避免冲突的发生。 如果要求能够支持高并发,那么乐观锁 。
事务
数据库访问层面是面向于数据库操作,其中比较重要的一个概念是事务:
事务的主要作用是保证数据库数据一致性,当多个用户使用事务操作数据库的同时可能会发生以下情况:
丢失更新、脏读、不可重复读 和 幻读
事务的ACID特性
原子性Automicity,一个事务内的所有操作,要么全做,要么全不做
一致性Consistency,数据库从一个一致性状态转到另一个一致性状态
独立性(隔离性)isolation, 一个事务在执行期间,对于其他事务来说是不可见的
持久性(Durability): 事务一旦成功提交,则就会永久性的对数据库进行了修改
事务的隔离级别
READ UNCOMMITED(未提交度) 事务之间的数据是相互可见的
READ COMMITED(提交读) 大多数数据库的默认隔离级别, 保证了不可能脏读,但是不能保证可重复读, 在这个级别里,数据的加锁实现是读取都是不加锁的,但是数据的写入、修改和删除是需要加锁的。
REPEATABLE READ (可重复读) 解决了不可重复读的问题,保证了在同一个事务之中,多次读取相同的记录的值的结果是一致的。 但是无法解决幻读。这个阶段的事务隔离性,在mysql中是通过基于乐观锁原理的多版本控制实现的。
SERIALIZABLE (可串行化读) 最高的隔离级别,解决了幻读 ,它会在读取的每一行数据上都进行加锁, 有可能导致超时和锁争用的问题。
死锁
死锁指两个事务或者多个事务在同一资源上相互占用,并请求对方所占用的资源,从而造成恶性循环的现象。
出现死锁的原因:
- 系统资源不足
- 进程运行推进的顺序不当
- 资源分配不当
产生死锁的四个必要条件
互斥条件: 一个资源只能被一个进程使用
请求和保持条件:进行获得一定资源,又对其他资源发起了请求,但是其他资源被其他线程占用,请求阻塞,但是也不会释放自己占用的资源。
不可剥夺条件: 指进程所获得的资源,不可能被其他进程剥夺,只能自己释放
环路等待条件: 进程发生死锁,必然存在着进程-资源之间的环形链
处理死锁的方法:
预防,避免,检查,解除死锁