锁机制是数据库一个比较重要的机制,在处理事务的并发性方面起着至关重要的作用,我也看过好多关于锁机制的文章blog,什么悲观锁、乐观锁?什么共享锁、排他锁?还有什么行级锁、表级锁?另外还有读锁、写锁?oh,my god!怎么这么多锁?今天终于静下心来好好研究梳理一番这些烦人的锁。
这篇文章我想弄明白三个个问题:
一是把上面这些锁都是什么,怎么定义的搞明白;
二是把这些乱七八糟的锁什么场景下使用、怎么使用搞明白;
三是这些锁和事务之间是什么关系。
一、锁的分类
1.1、乐观锁和悲观锁(从策略上划分)
乐观锁:顾名思义就是非常乐观,非常相信真善美,每次去读数据都认为其它事务没有在写数据,所以就不上锁,快乐的读取数据,而只在提交数据的时候判断其它事务是否搞过这个数据了,如果搞过就rollback。乐观锁相当于一种检测冲突的手段,可通过为记录添加版本或添加时间戳来实现。
悲观锁:对其它事务抱有保守的态度,每次去读数据都认为其它事务想要作祟,所以每次读数据的时候都会上锁,直到取出数据。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性,但随之而来的是各种开销。悲观锁相当于一种避免冲突的手段。
选择标准:如果并发量不大,或数据冲突的后果不严重,则可以使用乐观锁;而如果并发量大或数据冲突后果比较严重(对用户不友好),那么就使用悲观锁。
**注意**:首先明确一点乐观锁和悲观锁是一种编程策略,并不是数据库具有悲观锁和乐观锁。
悲观锁实现代码:
乐观锁两种实现方式:
一是使用数据版本(Version)记录机制实现。这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值+1。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。用下面的一张图来说明:
如上图所示,如果更新操作顺序执行,则数据的版本(version)依次递增,不会产生冲突。但是如果发生有不同的业务操作对同一版本的数据进行修改,那么,先提交的操作(图中B)会把数据version更新为2,当A在B之后提交更新时发现数据的version已经被修改了,那么A的更新操作会失败。
乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。
乐观锁实现示例:
1.2、共享锁和排它锁(从读写角度划分)
共享锁(S锁,Shared Lock):也叫读锁(Read Lock),持有S锁的事务只读不可写。如果事务A对数据D加上S锁后,其它事务只能对D加上S锁而不能加X锁。
排它锁(X锁,Exclusive Lock):也叫写锁(Write Lock),持有X锁的事务可读可写。如果事务A对数据D加上X锁后,其它事务不能再对D加任何锁,直到A对D的锁解除。
选择标准:数据库根据sql语句选择加什么锁
如何使用:数据库自身创建
1.3、表级锁和行级锁(从粒度角度划分)
表级锁(Table Lock):表级锁将整个表加锁,性能开销最小。用户可以同时进行读操作。当一个用户对表进行写操作时,用户可以获得一个写锁,写锁禁止其他的用户读写操作。写锁比读锁的优先级更高,即使有读操作已排在队列中,一个被申请的写锁仍可以排在所队列的前列。
行级锁(Row Lock):行级锁仅对指定的记录进行加锁,这样其它进程可以对同一个表中的其它记录进行读写操作。行级锁粒度最小,开销大,能够支持高并发,可能会出现死锁。
选择标准:数据库根据sql语句选择加什么锁
如何使用:数据库自行加锁
在使用以下语句时,Oracle会自动应用行级锁:INSERT、UPDATE、DELETE、SELECT … FOR UPDATE [OF columns] [WAIT n | NOWAIT];SELECT … FOR UPDATE语句允许用户一次锁定多条记录进行更新.使用commit或者rollback释放锁。
关于共享锁排它锁、表级锁行级锁的解释清参考下面:
最后自己总结下:
如果执行insert、update、delete、select .... for update语句,表上共享锁,对应的数据行上会加行级排它锁。该表不能执行ddl语句,对应的数据行不能执行update、delete(因为排它锁会阻塞,直到锁释放),可以执行select语句,其他行数据可以执行除ddl的任何记录。
执行ddl语句会加表级排它锁。表里的所有数据都不能执行dml语句。
也证明了行级锁只有排他属性。