lusiqi

mysql系列-事务,介绍事务的特性,隔离级别等。


简介

事务是作为单个逻辑工作执行的一系列操作,这些操作作为一个整体,一起向系统提交,要么都执行,要么都不执行。事务是一个不可分割的工作逻辑单元。

特性

事务必须具备以下四个特性,简称ACID:

原子性atomicity

一个事务必须被视为一个不可分割的最小单元,整个事务中的所有操作,要么全提交成功,要么全部失败会滚,对于一个事务来说,不可能只执行其中一部分的操作,这就是事务原子性

一致性consistency

数据库总是从一个一执行状态转换到另外一个一致性状态。多个事务对同一个数据读取结果是相同的。

隔离性Isolation

并发访问数据库时,一个用户的事务不被其他的事务所干扰,各并发事务之间数据是独立的。

持久性Durability

一个事务被提交后,它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有影响。

并发事务

多个事务并发运行,经常会操作相同的数据来完成各自的任务。并发会导致以下问题:

脏读Dirty read

当一个事务正在访问数据并且对数据进行了改变,而这种修改还没有提交到数据库中,这是另外一个事务也访问了这个数据,然后使用了这个数据,因为这个数据还没有提交数据,那么另外一个事务读到的这个数据是“脏数据”,依据脏数据所做的操作可能是不正确的。

丢失修改Lost to modify

指在一个事务读取一个数据时,另外一个事务也访问该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。

不可重复读Unrepearableread

指在一个事务内多次读同一数据,在这个事务还没结束时,另一个事务也访问该数据,那么在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不一样,这就发生了在一个事务内两次读取的数据是不一样的情况,因此称为不可重复读。

幻读Phantom read

幻读与不可重复读类似,它发生在一个事务1读取了几行数据,接着另一个并发事务2插入了一些数据时,在随后的查询中,事务1就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

不可重复读和幻读区别:

不可重复读的重点是修改,比如多次读取一条记录发现其中某些列的值被修改,幻读的重点在于新增或者删除,比如多次读取数据时,发现记录条数增多或减少。

事务隔离级别

SQL标准定义了四个隔离级别:

READ UNCOMMITTEd读取未提交

最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。从性能上来说,不会比其他的级别好太多,也缺乏其他级别的好处,实际应用中一半很少使用。

READ COMMITTED读取已提交

大多数数据库系统默认级别都是READ COMMITED(但mysql不是),允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或者不可重复读仍有可能发生。

REPEATABLE READ可重复读

对同一字段多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。InnoDB和XtraDB存储引擎通过多版本并发控制MVCC解决幻读问题。

可重复读时Mysql的默认事务隔离级别,通过SELECT @@tx_isolation;命令来查看隔离级别。

SERIALIZABLE可串行化

SERIALIZABLE是最高的隔离级别,完全服从ACID的隔离级别,通过强制事务串行化执行,避免了前面说的幻读问题,简单的说,SERIALIZABLE会在读取的每一行数据上都加上锁,所以会导致大量的超时和锁争用的问题。InnoDB存储引擎在分布式事务的情况下,一般会用SERIALIZABLE隔离级别。

隔离级别 脏读 不可重复读 幻读
READ UNCOMMITTED
READ COMMITTED ×
REPEARABLE READ × ×
SERIALIZABLE × × ×

锁机制

MyISAM采用表级锁table-level locking,InnoDB支持行级锁row-level locking和表级锁,默认行级锁。

表级锁

MYSQL中锁定粒度最大的一种锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁块,不会出现死锁。其锁定粒度最大,触发锁冲突的概率最高,并发读最低,MyISAM和InnoDB都支持表级锁。

行级锁

MYSQL中锁定粒度最小的一种锁,只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。

InnoDb存储引擎的行锁算法有三种:

  • Record lock:单个行记录上的锁
  • Gap lock:间隙锁,锁定一个范围,不包括记录本身,这样设计为了阻止多个事务将记录插入到同一范围,而导致幻读
  • Next-key lock:即Record lock+Gap lock,锁定一个范围,包含记录本身,InnoDb默认使用Next-key lock

MVCC

mysql的大多数事务型存储引擎实现的都不是简单的行级锁,基于提升并发性能的考虑,他们一般都实现了多版本并发控制MVCC。不仅是mysql,包括Oracle等数据库也实现了MVCC,但各自的实现机制不尽相同,因为MVCC没有一个统一的实现标准。

MVCC的实现,是通过保存数据在某个时间点的快照来实现的。也就是说,不管需要执行多长时间,每个事务看到的数据都是一致的。根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的。

不同存储引擎的MVCC实现是不同的,典型的有乐观并发控制悲观锁并发控制。InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存了行的过期时间或删除时间。当然存储的不是实际的时间值,而是版本号,每开始一个新的事务,系统版本号会自动递增。用来和查询到的每行记录的版本号进行比较。在REPEATTABLE READ重复读级别下,MVCC的具体操作:

SELECT

InnoDB会根据以下两个条件检查每行数据:

  • InnoDB只查找版本早于当前事务版本的数据行,也就是行的系统版本号小于或者等于事务的系统版本号。这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。
  • 行的删除版本要么未定义,要么大于当前事务版本号。这可以确保事务读取到的行,在事务开始之前未被删除。

只有符合上述两个条件的记录,才能返回作为查询结果。

INSERT

InnoDB为新插入的每一行保存当前系统版本号作为行版本号。

DELETE

InnoDB为删除的每一行保存当前系统版本号作为删除标识。

UPDATE

InnoDB为插入一行新纪录,保存当前系统版本号作为行版本号,同时保存当前版本号到原来的行作为行删除标识。

保存这两个额外的系统版本号,使得大多数的读操作可以不用加锁。这样的设计使得数据读操作简单,性能很好,并却也能保证只会读取到符合标准的行。不足之处就是每行记录都需要额外的存储空间,需要做更多的行检查工作。

MVCC只在REPEATABLE READ和READ COMMITTED两个隔离级别下工作,其他两个隔离级别都和MVCC不兼容,因为READ UNCOMMITTED总是读取最新的数据行,而不是符合当前事务版本的数据行。而SERIALIZABLE会对所有的行都加锁。

 评论