揭秘MySQL事务与锁的联系

来源:互联网 发布:js基本数据类型有哪些 编辑:程序博客网 时间:2024/06/11 17:13

MySQL事务的概念

事务是指在一个或多个SQL语句操作的过程中,最后的结果要么全部成功要么全部失败,将这样一系列操作看作一个操作的基本操作单元。整个操作单元中不可分割,只要有一个操作失败,则全部操作都会回滚。具备ACID特性,不再说明。

事务的可串行化

如前所述,事务是将多个操作看成一个整体操作,事务的可串行化解决的就是多个事务并发访问操作的顺序对数据一致性没有影响。当存在多个事务访问操作同一个数据时,如果可以通过一种合适的顺序调度事务的执行,既满足了访问数据的正确性又满足了整体数据的一致性,则称为事务的可串行化调度。
可串行化的概念作用于保证并发的事务调度方式既能满足数据一致性需求,又能提高并发事务的执行效率。(突然想起了线程安全的概念

事务中的锁机制

事务的隔离性的保证是建立在锁的基础上的(MVCC也可以保证,后面细说)。在事务中最常见的是读锁(S锁/共享锁)写锁(X锁/排他锁),事务一般遵守两段式协议,即一个事务分为加锁和解锁两个阶段,加锁阶段不会解锁,解锁阶段不会加锁;

其实单单的拿出来谈事务的锁机制比较干燥,如果是放在MySQL中再来谈锁机制,其实还会有不同存储引擎的锁机制,这里我们先引入几个和存储引擎相关的锁:

  • 行级锁:锁的粒度是一行记录,开销大,加锁慢,会出现死锁;锁的粒度是最小的,发生锁冲突的概率最小,并发度最高
  • 表级锁:锁的粒度是一张表,开销小,加锁块,不会出现死锁,锁的粒度是最大的,发生锁重读的概率最高,并发度最低
  • 页面锁:开销和加锁时间介于行锁和表锁之间;会出现死锁,粒度一般,并发度一般

我们要确定事务的加锁情况是需要有前提条件的,如系统的隔离级别,搜索的条件是否是索引,以及索引是否唯一等,具体的可以参考:mysql事务和锁InnoDB的SQL加锁实现分析部分,讲解的很好,不敢再班门弄斧。

MySQL中常用的存储引擎

MySQL中默认的存储引擎是MyISAM,其次使用的最多的是InnoDB,还有MEMORYMERGEARCHIVE。后面三个存储引擎使用的较前两个少,这是因为他们之间的特性决定的。

  • MyISAM:MySQL 5.4及以前的的默认存储引擎,不支持事务,也没有行级锁和外键,默认就是使用表锁。在执行Insert和Update操作时(写操作),会锁定整张表。但MyISAM引擎保存了表的行数,但进行诸如Select count(*)操作时,MyISAM可以直接读取已经保存的值而不需要扫描全表;执行Insert语句较快。当应用进行的读操作远多与写操作,并不需要支持事务时,MyISAM是首选。
  • InnoDB:MySQL 5.5以后的默认存储引擎,该引擎提供对数据库的ACID**事务的支持和行级锁、表级锁以及外键支持**。所以在分布式场景下MySQL的存储引擎大都使用InnoDB。其设计的本意就是为了处理大容量数据的数据库系统,InnoDB在运行时会在内容建立缓冲池,用于缓冲数据和索引。但由于没有保存表中数据的行数,所以执行诸如Select count(*)语句时会需要扫描全表;InnoDB也支持表锁,当查询的字段是索引时,会使用行锁,但若不是索引,那么还是会锁表同时进行全表扫描;执行Update语句较快。但由于粒度较小,并发场景下效率更高。InnoDB还支持事务处理和故障恢复。
  • MEMORY:该引擎的出现就是为了提供更快的速度,所以是基于系统内存实现的存储引擎,所有的数据都是存储在内存中,因而会有数据不可恢复的风险。支持B+树索引和散列索引。

综上所述加上一些实际的应用场景统计,优先选择使用MyISAM引擎更优。MyISAM和InnoDB使用索引的数据结构都是B+树,前者是非聚簇索引后者是聚簇索引。

MySQL如何设置事务隔离级别和使用锁

在这个部分我们可能会来学习一下操作MySQL事务隔离级别和使用锁的SQL语句。

显式使用事务

START TRANSACTION;/BEGIN;...// CURD操作...SAVEPOINT 'name';...COMMIT;...ROLLBACK;ROLLBACK TO SAVEPOINT 'name';

事务隔离级别的设置

首先得保证是InnoDB存储引擎

  • 修改my.ini文件,加上transaction-isolation = REPEATABLE-READ,可选参数有READ-UNCOMMITTED, READ-COMMITTED, REPEATABLE-READ, SERIALIZABLE
  • 执行SQL:set session transaction isolation level read uncommitted;,同样可选择上述参数

使用锁

显式的使用锁

  • 表锁:LOCK TABLES <tableName>,<tableName>...UNLOCK TABLES <tableName>...
  • 行锁:在InnoDB中使用行锁是通过索引上的索引项来实现的,只有通过索引条件去查找数据才会使用行锁,否则就是表锁

事务隔离级别的实现

我们要有对知识的探讨钻研精神,只知道事务的隔离级别在我看来至少是还不够的,现在再把目光放到它们的实现原理上来。

首先再回顾一下MySQL事务隔离级别:读不提交、读提交(RC)、可重复读(RR)、序列化。它们的区别不再赘述。

MySQL默认的事务隔离级别

MySQL默认的事务隔离级别是RR(Repeatable Read),按照我们理解的,可重复读RR只能解决不可重复读的问题(一个事务的两次读取数据不一致),还是会有幻读的可能发生,但是MySQL中有特殊的机制保证在RR级别不会出现幻读。

MySQL中MVCC机制

MVCC全称(Multi-Version Concurrency Control),在MVCC中读操作可以分为两种:快照读和当前读。在隔离级别为序列化时,MySQL会取消快照读,所有的读操作也会加读锁

  • 快照读:简单的Select操作,不加锁(也会有例外)
  • 当前读:特殊的读操作,Insert、Update、Delete操作,需要加锁

回过头来看MVCC机制,锁机制可以控制并发操作,但是其系统开销大,而MVCC可以在大多数情况下代替行锁,降低了系统开销。MVCC主要是针对RR级别做的,我们主要来看看InnoDB中的MVCC。

在这种情况下,每行记录还会保存了两个不同的时间点在两个隐藏的列中,第一列保存的是行的创建时间,第二行保存的是行的删除时间(未删除是undefined);这里存储的并不是实际的时间,而是事务的ID(系统的版本号),每开启一个事务,系统的版本号就会自动加1。然后我们来看看不同操作时MVCC机制的体现:

  • Insert:InnoDB在MVCC机制中,会在创建时间隐藏列更新为当前的事务ID,删除时间列是undefined
  • Select:MVCC中只会返回创建时间小于或等于当前事务ID且删除时间要么是undefined要么大于当前事务ID的记录,两个条件都满足的记录才是正确的
  • Delete:MVCC会在删除时间列更新当前事务ID,然后真正删除是由MySQL后台运行的独立线程去定时清理的
  • Update:MVCC中的Update被拆分成了Insert和Delete操作,显示插入一个更新后的记录(主键可能重复),然后标记原始记录,等待其被删除

MVCC机制中主键冲突怎么办?

在上面的解释中,Update会被拆分成Insert和Delete两个操作,那么势必会同时出现拥有同一个主键的两条记录,这显然违背了我们之前“主键是唯一的”的认知。
其实在MVCC机制下,这条记录虽然主键相同,但是两条记录的创建时间和删除时间是不同的,Select操作时肯定会略过那条已经过期的数据,更何况,这条已经被标记删除的记录马上就会被MySQL后台运行的独立线程清理,所以仍然可以对外呈现主键唯一的样子。

MySQL如何保证RR隔离级别不出现幻读?

其实上面很多内容讲述了MVCC机制,应该已经不难猜到MySQL就是使用了MVCC机制来保证了RR隔离级别却仍然不会出现幻读。
MVCC保证了Select操作时选取的记录的创建时间不大于当前事务的ID且删除时间大于当前事务的ID记录,当幻读的条件出现时,它的创建时间一定是大于当前事务的ID的,这并不会出现在最后的结果中,因此保证了RR隔离级别不会出现幻读现象。

其实还有一个GAP锁的概念,它是在记录之间的空隙进行加锁,防止访问的数据之间再加入新的数据记录,以达到不会出现幻读现象。这个GAP锁也是RR隔离级别在锁机制层面上保证不会出现幻读的关键,这里作为扩展提一下。

总结

主要是回顾了学习了事务相关的种种概念和知识点,感觉还是很有收获的,很多不懂的地方现在也解释的通了。贴上参考学习的链接:

  • 数据库事务隔离级别– 脏读、幻读、不可重复读(清晰解释)
  • mysql事务和锁InnoDB
  • mysql的常用引擎
  • MySQL存储引擎总结
  • MySQL存储引擎--MyISAM与InnoDB区别
  • 关于innodb中MVCC的一些理解
  • 轻松理解MYSQL MVCC 实现机制
  • 初步理解MySQL的gap锁
  • MySQL设置事物隔离级别
  • mysql事务处理用法与实例详解
  • MySQL中的锁(表锁、行锁)