数据库的乐观锁与悲观锁
概述
无论是悲观锁还是乐观锁,都是人们定义出来的概念,是一种读取和修改数据的并发访问策略,由应用和业务需求来确定的。其实不仅仅是数据库系统中有乐观锁和悲观锁的概念,像memcache、hibernate、tair等都有类似的概念。所以,不要把乐观锁和悲观锁狭义的理解为DBMS中的概念,更不要把他们和数据中提供的锁机制(行锁、表锁、排他锁、共享锁)混为一谈。在DBMS中,只是利用数据库本身提供的锁机制和数据的版本(version)来实现的这两种不同的并发访问策略。
悲观锁
正如其名,它指的是对数据被外界修改持保守态度,认为数据是会经常变动的。因此,在整个数据处理过程中,会将数据保持在锁定状态。悲观锁的实现,是依靠数据库提供的锁机制。在悲观锁的情况下,为了保证事务的隔离性,就需要一致性锁定读。读取数据时给加锁,其它事务无法修改这些数据。修改删除数据时也要加锁,其它事务无法读取这些数据。
基本是从SQL语句上体现悲观锁的:
mysql select columnName from TableName where id=1 for update oracle select empno,ename from emp where empno=‘7369‘ for update (nowait)
乐观锁
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。而乐观锁机制在一定程度上解决了这个问题,其大多是基于数据版本(Version)记录机制实现。何谓数据版本?
在Oracle数据库中,在数据块头部记录事务(不管是提交还是未提交)的最大SCN,同时头部还记录了事务表(ITL),用于描述该数据块的事务情况(是否提交、SCN、锁信息和使用回滚段的位置)。当一条select语句开始执行时,数据库会确定查询开始执行时的SCN。为了保证检索的数据是已提交的数据,或防止数据正在被修改,那么检索的数据块所记录的SCN不能大于查询执行时的SCN。如果读取到的数据块的SCN大于查询执行时的SCN,说明有数据在查询开始后做了修改,这是就需要利用本数据块块头中的ITL来确定是哪些事务在查询开始后处理过数据(也就是判断各事务处理的SCN,不管是提交还是未提交),然后根据事务去回滚段读取满足查询条件的数据。这种方式叫做多版本并发控制(MVCC)。通过MVCC,读操作不用加锁,且操作很简单,性能很好,并且也能保证只会读取到符合标准的行。
在MySQL的InnoDB中,会在每行数据后添加两个额外的隐藏的值来实现MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。 在实际操作中,存储的并不是时间,而是事务的版本号,每开启一个新事务,事务的版本号就会递增。在可重读Repeatable reads事务隔离级别下:
- SELECT时,读取创建版本号<=当前事务版本号,删除版本号为空或>当前事务版本号。
- INSERT时,保存当前事务版本号为行的创建版本号。
- DELETE时,保存当前事务版本号为行的删除版本号。
- UPDATE时,插入一条新纪录,保存当前事务版本号为行创建版本号,同时保存当前事务版本号到原来删除的行。