并发锁 (四) : innodb 事务

2019-12-18 15:07:18 浏览数 (1)

innodb

从Mysql5.5版本开始,InnoDB是默认的表存储引擎。其特点是行锁设计、支持MVCC、支持外键、提供一致性非锁定读、同时被设计用来最有效的利用以及使用内存和CPU。

它和myisam最大的不同点在于,一个使用锁来做并发控制,一个使用MVCC做并发控制.

MVCC

MVCC(Multi-Version Concurrency Control)多版本并发控制,也可称之为一致性非锁定读;它通过行的多版本控制方式来读取当前执行时间数据库中的行数据。实质上使用的是快照数据,这样就可以实现不加锁读。MVCC 主要应用于 Read Commited (提交读(不可重复读))和 Repeatable read (可重复读)两个事务隔离级别。

基本特征

  • 每行数据都存在一个版本,每次数据更新时都更新该版本。
  • 修改时Copy出当前版本随意修改,各个事务之间无干扰。
  • 保存时比较版本号,如果成功(commit),则覆盖原记录;失败则放弃copy(rollback)

在innodb中,每次执行sql语句都会开启事务用于实现mvcc

实现策略

在每一行数据中额外保存两个隐藏的列:当前行创建时的版本号和删除时的版本号(可能为空,其实还有一列称为回滚指针,用于事务回滚,不在本文范畴)。

这里的版本号并不是实际的时间值,而是系统版本号。

每开始新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询每行记录的版本号进行比较。

每个事务又有自己的版本号,这样事务内执行CRUD操作时,就通过版本号的比较来达到数据版本控制的目的。

innodb下curd是怎么工作的

1:insert,执行以下sql语句:

代码语言:javascript复制
insert into test (name) value ('test');

则同时 INFORMATION_SCHEMA.INNODB_TRX 表会一条记录,记录该次改动的版本号,例如是1,则:

2:之后,在更新操作中:

代码语言:javascript复制
update test set name= 'new_value' where id=1;

先标记旧的那行记录为已删除,并且删除版本号是事务版本号,然后插入一行新的记录的方式。

3:删除操作的时候,就把事务版本号作为删除版本号。

代码语言:javascript复制
delete from test where id=1;

4:查询

从上面的描述可以看到,在查询时要符合以下两个条件的记录才能被事务查询出来:

1) 删除版本号未指定或者大于当前事务版本号,即查询事务开启后确保读取的行未被删除。(即上述事务id为2的事务查询时,依然能读取到事务id为3所删除的数据行)

2) 创建版本号 小于或者等于 当前事务版本号 ,就是说记录创建是在当前事务中(等于的情况)或者在当前事务启动之前的其他事物进行的insert。

(即事务id为2的事务只能读取到create version<=2的已提交的事务的数据集)

在内部,InnoDB为数据库中存储的每一行添加三个字段。

一个6字节的DB_TRX_ID字段来表示插入或者修改这一行的最后一个事务的事务标示符。

delete在内部被当做update来对待,就是在行的特定位做一个标记,以表明这一行已经被删除。

每一行还包含一个被叫做“roll pointer”的7字节的DB_ROLL_PTR字段。这个roll pointer被指向一个写在回滚段中的undo log。如果行被修改了,那么这个undo log记录包含的信息必须先于行修改被重新修改。一个6字节的DB_ROW_ID字段包含一个当行被插入的时候单调递增的行ID。如果InnoDB自动生成了一个聚集索引,那么这个索引包含行ID值,否则DB_ROW_ID列不会出现在任何索引中。

事务

类似于myisam的每次执行都会隐式加锁,innodb中,每次执行sql都会隐式的开启事务,在语句结束之后提交事务或回滚事务

事务满足了ACID  原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。4个条件 

  • 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
  • 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
  • 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
  • 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

除了隐式的开启事务之外,事务一般用于处理操作量大,复杂度高的数据

例如用户充值需要更新充值表状态,以及用户余额,为了避免在更新的时候,成功更新充值表,但是却突然出错,没有更新用户余额的情况,可以增加事务.

开启事务

在innodb中,每条语句都是默认一个事务,无需手动开启.关于事务有关的sql语句如下:

  • BEGIN 或 START TRANSACTION 显式地开启一个事务;
  • COMMIT 也可以使用 COMMIT WORK,不过二者是等价的。COMMIT 会提交事务,并使已对数据库进行的所有修改成为永久性的;
  • ROLLBACK 也可以使用 ROLLBACK WORK,不过二者是等价的。回滚会结束用户的事务,并撤销正在进行的所有未提交的修改;
  • SAVEPOINT identifier,SAVEPOINT 允许在事务中创建一个保存点,一个事务中可以有多个 SAVEPOINT;
  • RELEASE SAVEPOINT identifier 删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;
  • ROLLBACK TO identifier 把事务回滚到标记点;
  • SET TRANSACTION 用来设置事务的隔离级别。InnoDB 存储引擎提供事务的隔离级别有READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ 和 SERIALIZABLE。

事务保存点允许我们可以在mysql事务处理过程中定义保存点(SAVEPOINT),然后回滚到指定的保存点前的状态。但是将破坏事务的(ACID)

并发事务处理出现的问题

根据mvcc的工作原理,我们不难看出,它是通过版本号来做数据更新的,那么在并发下,就会出现以下问题:

更新丢失(Lost Update)

当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题(最后的更新覆盖了由其他事务所做的更新。)例如:

例子1:

该例子本人不能复现,可忽略

例子2:

当其中一个事务提交/回滚比另一个事务慢的时候,另一个事务的更新则会丢失

该例子本人不能复现,可忽略

脏读(Dirty Reads)

一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做"脏读"。

例如:

这个时候,当事务A回滚之后,就会出现明明金额没有1000,最终金额还是扣除了1000,变成了0

不可重复读(Non-Repeatable Reads):

一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读”。

例如:

本来事务B读取用户1余额为900,可再次读取却变成了1000

幻读(Phantom Reads):

一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。

例如:

在这个例子中,事务A和事务B本身读取的时候,账号"tioncico"是不存在的,所以二者之间同时插入了一条tioncico的数据,导致一下子多出了2条(如果是account做唯一索引的话,事务A还会出现插入失败的情况)

事务隔离级别

在上面讲到了在并发下,事务可能会出现以上的问题,那么该怎么解决呢?

脏读、不可重复读和幻读,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。

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

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

2:另一种是不用加任何锁,通过一定机制生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供一定级别(语句级或事务级)的一致性读取。从用户的角度来看,好像是数据库可以提供同一数据的多个版本,因此,这种技术叫做数据多版本并发控制(MultiVersion Concurrency Control,简称MVCC或MCC),也经常称为多版本数据库。

读数据一致性及并发副作用

读数据一致性

脏读

不可重复读

幻读

为提交读(read uncommitted)

最低级别,不读物理上的数据

已提交读(read committed)

语句级

可重复读(Repeatable read)

事务级

可串行化(Serializable)

最高级别,事务级

Read Uncommitted(读取未提交内容)

       在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。

Read Committed(读取提交内容)

       这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。

Repeatable Read(可重读)

       这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。

Serializable(可串行化) 

       这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。

mysql事务隔离级别

在mysql中,默认事务隔离级别为 可重复读(Repeatable read)

mysql事务隔离级别相关命令:

1.查看当前会话隔离级别

代码语言:javascript复制
select @@tx_isolation;

2.查看系统当前隔离级别

代码语言:javascript复制
select @@global.tx_isolation;

3.设置当前会话隔离级别

代码语言:javascript复制
set session transaction isolation level repeatable read;

4.设置系统当前隔离级别

代码语言:javascript复制
set global transaction isolation level repeatable read;

0 人点赞