事务隔离级别(二)

今天从MySQL数据库的角度说明锁机制和事务隔离级别之间的关系。

MySQL数据库支持多种数据库引擎,但是最常用的只有MyISAM和InnoDB。由于MyISAM不支持事务,因此当我们在谈到事务隔离级别的时候,基本都是在说InnoDB。不过由于MyISAM上使用的表级锁对于理解InnoDB上的行级锁大有裨益,因此本文也会扼要介绍。

一、并发事务与事务隔离级别

相对于单线程的数据处理来说,并发事务能大大提高数据库资源的利用率,提高数据库系统的事务吞吐量,从而可以支持更多用户。但是并发也会带来一些问题:

  • 丢失更新:多个事务同时对同一行数据更新,然后基于最初选定的值实施操作时,由于事务的隔离性使得互相并不知道对方的存在。从而造成最后的更新覆盖其他所有事务的更新。
  • 脏读:一个事务正在对一条记录做修改,在事务提交前,当前记录处于不稳定状态;这时另一个事务也来读取这条记录,如果不加控制,第二个事务可能会读取脏数据并产生对未提交数据的依赖。这种现象叫“脏读”。
  • 不可重复读:一个事务读取某些数据后的某个时间,再次尝试读取以前读过的数据,取发现数据已经被修改或删除。这种现象就叫做“不可重复读”。
  • 幻读:一个事务按照相同的条件查询之前检索过的记录,却发现有其它事务插入了新的数据。这种现象就称为“幻读”。

丢失更新不能完全依靠数据库事务控制器来解决,需要应用程序对需要更新的数据加必要的锁来解决。因此,防止丢失更新是应用程序的责任。从线程并发的角度来说,应该在可能更新数据的方法上进行同步。

数据库实现事务隔离的方式,基本上可分为两种:

一种是在读取数据前,对其加锁,阻止其它事务对数据进行修改。

另一种是不加任何锁,而是通过一定机制生成一个数据请求点的一致性数据快照,并用这个快照来提供一定级别的一致性读取操作。

数据库的事务隔离越严格,并发的副作用越小,但付出的代价就越大。因为事务隔离实质上就是使事务在一定程度上“序列化”,这显然与并发是相矛盾的。为了解决上述矛盾,MySQL定义了4种事务隔离级别,分别叫:未提交读取、已提交读取、可重复读取、可序列化。具体定义请参考上一篇文章,这里不赘述了。

二、InnoDB的行级锁模式

InnoDB提供了两种类型的行级锁和与之匹配的表级锁。

  • 行共享锁:允许其它事务的读取但阻止获取排他锁。
  • 行排他锁:允许当前事务读取和更新数据,但阻止其它事务取得操作数据集的行共享锁与行排他锁。
  • 表共享锁(意向共享锁):事务打算给数据行加行共享锁,需要先取得该数据表的表共享锁。
  • 表排他锁(意向排他锁):事务打算给数据行加行排他锁,需要先取得该数据表的表排他锁。

需要注意,当一个事务获得一个数据表的表排他锁时,另一个事务也可以获取这个数据表的排他锁。因为两个更新数据的事务彼此不会冲突,而丢失更新的问题应该交给应用层解决。由此可见,InnoDB对锁的实现显然比字面表述更加复杂。这就是为什么我们通常称InnoDB的表级锁为意向锁的原因,它和MyISAM的表级锁有根本性区别。

至于具体加锁的操作,不在本文的讨论范围。

三、MyISAM的表级锁模式

MyISAM引擎不支持事务,因此不存在所谓的事务并发问题。MyISAM的表级锁有两种模式:表共享读锁和表排他写锁。除了语义上的不同以外,与InnoDB的区别主要是以下两点:

  • MyISAM给表加锁是同时取得所有涉及表的锁,而InnoDB则是逐步获取锁。因此MyISAM不会出现死锁现象。反过来说,正是由于这个特点的存在,实际运行中写进程优先于读进程。因此MyISAM可能会出现读进程永远被阻塞的情况,InnoDB却不会遇到这样的问题。
  • 使用MyISAM引擎获取了共享读锁的线程只能进行读取操作,而不能进行更新与增加,无论当前时刻是否有其它线程在操作数据表。并且MyISAM无法自动提升锁级别,因此取得共享锁的线程要更新或增加数据必须先放开共享锁再尝试获取排他锁。InnoDB引擎能够自动提升锁级别,读取事务能够在当前数据集没有其它事务访问的情况下更新相关数据,效率比较高。

四、InnoDB行级锁的特殊性

能够理解以上内容,基本就对MySQL的锁机制和事物隔离有了比较准确的认知。作为补充,增加一些MySQL数据库与Oracle的区别。

InnoDB行级锁时通过给索引上的索引项来加锁实现的,这一点与Oracle不同,后者是通过在数据块中给相应的数据行加锁来实现的。这个特点意味着:只有通过索引条件检索数据,InnoDB才能使用行级锁,否则只能使用表级锁,从而极大的影响并发性能。此外,即使是访问不同记录,如果事务使用了相同的索引键也会出现锁冲突。

当我们使用范围索引并请求共享锁或排他锁的时候,对于键值不存在的记录,InnoDB会使用间隙锁。例如:

Select * From user where id>100 for update;

InnoDB不仅会给符合条件的所有记录加锁,也会给id>100的不存在的记录加间隙锁。使用间隙锁的目的主要是防止幻读的发生。这一点与MyISAM也有不同。使用MyISAM引擎被读线程加了共享读锁的数据表依然可以被其他写线程从表尾插入记录。因此,除非使用前文描述的“快照”技术,否则很难保证使用MyISAM的读操作不出现“幻读”问题。

原文地址:https://www.cnblogs.com/learnhow/p/5238172.html