锁的目的
解决多事务并发时造成的问题:脏读、不可重复读、幻读。
- 脏读:事务A读取了事务B尚未提交的修改删除操作,然后B事务由于某种原因回滚,则A读到的数据并不存在,为脏数据。
- 不可重复读:A多次读取某个数据,中间B对数据进行修改,导致读取的结果不一致。
- 幻读:A多次读取某个条件的数据,中间B插入了一些数据,导致读取的结果数量不一致。
通过锁机制可以实现事务的隔离,从而解决并发时的问题,隔离级别有4种
- 读未提交:最低等级的隔离,3个并发问题都没有解决,相当于事务之间操作完全可见。
- 读提交(不可重复读):只有提交了的事务操作才能被其它事务察觉,这样可以避免脏读,但是仍然不可重复读。
- 可重复读:通过事务级别的行锁(读写锁)实现,对某些索引行数据加锁,事务读时其他事务可以读,但是写时其他事务不能读写,不过存在插入操作会导致幻读,因为只锁定了某个行,并没有对区间内不存在的值进行锁定,依然可以插入。
- 序列化:通过事务级别的间隙锁甚至表锁实现,不同事务对某张表或者某个区域串行操作,避免幻读,相当于对某个索引区域加锁,其他事务无法插入数据,避免幻读,但是并发性太差,很少使用表锁。
mysql默认隔离级别是可重复读,使用索引时默认加next-key(行锁,间隙锁),应尽量避免行锁自动升级为表锁--不使用索引,对表大量数据修改,都会使mysql进行全表扫描,因此升级为表锁。上文所描述的是悲观锁,可以解决不可重复读和幻读的问题,读时进行数据锁定,不允许写,写时同样,不允许读和写。因为对数据是否会被外界改动持保守态度,比较悲观。
不过悲观锁性能较差,比较成熟的数据库如mysql,oracle等为了提高性能,采用MVCC多版本并发控制(乐观锁为理论基础)来解决不可重复读和幻读问题。
悲观锁
正如其名,它指的是对数据被外界(包含本系统当前的其它事务,以及来自外部系统的事务处理)改动持保守态度。因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现。往往依靠数据库提供的锁机制(也仅仅有数据库层提供的锁机制才干真正保证数据訪问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会改动数据)。在悲观锁的情况下,为了保证事务的隔离性,就须要一致性锁定读。读取数据时给加锁,其他事务无法改动这些数据。改动删除数据时也要加锁,其他事务无法读取这些数据。
乐观锁
相对悲观锁而言,乐观锁机制採取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销。特别是对长事务而言,这种开销往往无法承受。而乐观锁机制在一定程度上攻克了这个问题。乐观锁,大多是基于数据版本号( Version )记录机制实现。何谓数据版本号?即为数据添加一个版本号标识,在基于数据库表的版本号解决方式中,通常是通过为数据库表添加一个“version” 字段来实现。读取出数据时,将此版本号号一同读出,之后更新时,对此版本号号加一。
MVCC没有固定的实现方式,不同数据库不相同,下面是Innodb的MVCC方法:
在InnoDB中,会在每行数据后加入两个额外的隐藏的值来实现MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。 在实际操作中。存储的并非时间,而是事务的版本,每开启一个新事务,事务的版本就会递增。 在可重读Repeatable reads事务隔离级别下:
SELECT时。读取创建版本<=当前事务版本。删除版本为空或>当前事务版本。
INSERT时,保存当前事务版本为行的创建版本
DELETE时,保存当前事务版本为行的删除版本
UPDATE时,插入一条新纪录。保存当前事务版本为该新行的创建版本,同时作为修改之前的行数据的删除版本。
SELECT时。读取创建版本<=当前事务版本。删除版本为空或>当前事务版本。
INSERT时,保存当前事务版本为行的创建版本
DELETE时,保存当前事务版本为行的删除版本
UPDATE时,插入一条新纪录。保存当前事务版本为该新行的创建版本,同时作为修改之前的行数据的删除版本。