MySQL事务管理

目录

认识事务

事务的概念

事务的四大特性

支持事务的数据库引擎

事务的提交方式

事务的相关演示

事务的隔离级别

查看与设置隔离级别

读未提交(Read Uncommitted)

读提交(Read Committed)

可重复读(Repeatable Read)

串行化(Serializable)

隔离级别总结

一致性的理解

多版本并发控制

记录中的3个隐藏字段

undo日志

画图理解MVCC

Read View

RR与RC的本质区别


认识事务


事务的概念

概念:

事务是一组操作,这些操作要么全部执行成功,要么全部失败回滚。

解释:

1.操作的意思就是一条或多条SQL语句组成,这些语句在逻辑上存在相关性,共同完成一个任务。

2.而操作要么全执行成功,要么全失败回滚的属性就是事务的原子性,除此之外事务还有持久性、隔离性、一致性。

为什么出现事务?

解决用户访问数据库的潜在问题,比如网络异常,服务器宕机,数据不一致,并发冲突,数据丢失。

事务的四大特性

原子性(Atomicity):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中如果发生错误,则会自动回滚到事务开始前的状态,不会对数据库产生任何影响。

持久性(Consistency):事务提交后,对数据库的修改就是永久的,即使系统崩溃也将保存在数据库中。

隔离性(Isolation):数据库允许多个事务同时访问同一份数据,隔离性保证事务在并发执行和串行结果一致。

一致性(Durability):在事务的开始和结束,数据库的完整性约束必须得到保持。

支持事务的数据库引擎

MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务, MyISAM 不支持。
使用 show engines; 以行显示数据库引擎:
使用 show engines \G(不用加;) 以列显示数据库引擎:

说明:

Engine:存储引擎的名称。

Support:服务器对存储引擎的支持级别,YES表示支持,NO表示不支持,DEFAULT表示数据库默认使用的存储引擎。

Comment:存储引擎的简要说明。

Transaction:表示存储引擎是否支持事务,可以看到MySQL中只有InnoDB存储引擎支持事务。

XA:表示存储引擎是否支持XA事务。

Savepoints:表示存储引擎是否支持保存点。

事务的提交方式

事务常见的提交方式有两种,分别是自动提交和手动提交。 

通过show variables like 命令查看全局变量 autocommit,可以查看事务的自动提交是否被打开:

ON表示自动提交打开,OFF表示自动提交关闭。

用 SET 来改变 MySQL 的自动提交模式。

SET AUTOCOMMIT=0 禁止自动提交 :
SET AUTOCOMMIT=1 开启自动提交 :

事务的相关演示

演示1:事务的开始和回滚

执行下面SQL:

//1.建表
//2.查看提交方式
//3.开始事务
//4.设置保存点1
//5.插入数据1
//6.设置保存点2
//7.插入数据2
//8.查看数据
//9.回滚到保存点2
//10.查看数据,数据2消失了
//11.回滚到最初
//12.查找数据,数据1也消失了
create table if not exists account(
id int primary key,
name varchar(50) not null default '',
blance decimal(10,2) not null default 0.0
)ENGINE=InnoDB DEFAULT CHARSET=UTF8;

show variables like 'autocommit';

start transaction;

savepoint save1;

insert into account values (1, '张三', 100);

savepoint save2;

insert into account values (2, '李四', 10000);

select * from account;

rollback to save2;

select * from account;

rollback;

select * from account;

演示结果:rollback可以回滚到指定保存点或事务最开始的状态。

 演示2:证明未commit,客户端崩溃,MySQL自动会回滚(隔离级别设置为读未提交)

执行SQL:

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

2.设置当前会话隔离级别为读未提交

3.查看表

4.开始事务

5.插入数据

6.提交事务

7.使用ctrl + 、异常终止MySQL

8.在终端B上查看表

select @@session.tx_isolation;
set session transaction isolation level READ UNCOMMITTED;
select * from account;
begin ;
insert into account values(1,'张三',100);
commit;

//终端B
select * from account;

输出:

异常终止终端A后在终端B仍然可以看到数据,可见commit的数据持久化了:

演示3:begin操作会自动更改提交方式(事务中的所有的SQL语句都需要显示使用COMMIT语句进行提交),不受MySQL是否自动提交影响

执行SQL:

修改自动提交为OFF,开启事务,往表插入数据,使用ctrl + \异常终止MySQL,在终端B查看数据是否回滚。

select * from account;
show variables like 'autocommit';
set autocommit=0;
show variables like 'autocommit';
begin;
insert into account values(2,'李四',10000);
select * from account;

//终端B
//终端A崩溃前
select * from account;
//终端A崩溃后
select * from account;

输出:

演示4:证明单条SQL与事务的关系

执行SQL:

//实验一
select * from account;
show variables like 'autocommit';
set autocommit=0;
insert into account values(2,'李四',10000);

//终端B
//终端A崩溃前
select * from account;

//终端A崩溃后
select * from account;


//实验二
//终端A
show variables like 'autocommit';
set autocommit=1;
show variables like 'autocommit';
select * from account;
insert into account values(2,'李四',10000);
select * from account;

//终端B
//终端A崩溃前
select * from account;

//终端A崩溃后
select * from account;

做两个实验,一个是自动提交模式,一个是手动提交,验证数据的持久性。

实验一(手动提交),终端A:

实验一,终端B:(可见手动提交下,单条SQL不具有持久性)

实验二(自动提交),终端A:

实验二(自动提交),终端B:

结论总结:

1.begin或者start transaction开始的事务,事务便必须要通过commit提交,才会持久化,与是

否设置set autocommit无关。
2.事务可以手动回滚,同时,当操作异常,MySQL会自动回滚。
3.InnoDB 每一条 SQL 语言都默认封装成事务,自动提交。

4.如果没有设置保存点,也可以回滚,只能回滚到事务的开始。直接使用 rollback(前提是事务还没有提交) 。

5.如果一个事务被提交了(commit),则不可以回退(rollback) 。
6.事务执行中可以选择回退到哪个保存点。
7.InnoDB 支持事务, MyISAM 不支持事务。

事务的隔离级别

基本概念:

脏读:一个事务在执行过程中读取到了另一个事务未提交的数据。

不可重复读:事务在执行过程中,读取到的某个数据后,另一个事务修改了该数据,导致第一个事务重新读取数据的结果不一致。

幻读:一个事务在读取某个范围的数据时,另一个事务插入了新的数据,导致第一个事务重新读取该范围的数据时,发现有新的数据出现。

读未提交【Read Uncommitted】:所有的事务都可以看到其他事务没有提交的执行结果。相当于没有隔离性,会导致脏读、幻读、不可重复读问题。

读提交【Read Commited】:该隔离级别的大多数数据库的默认隔离级别,在事务中只能看到其他的已经提交的事务所做的改变。这种隔离级别会引起不可重复读,即一个事务执行时,多次select可能结果不一样。

可重复读【Repeatable Read】:MySQL默认的隔离级别,确保同一个事物在执行中,多次读取操作数据时,会看到同样的数据行。但是会有幻读问题。

串行化【Serializable】事务的最高隔离级别,通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。它在每个读的数据行上加上共享锁,可能会导致超时和锁竞争(效率低)。

隔离级别如何实现:通过锁实现,常见锁有表锁、行锁、读锁、写锁、间隙锁(GAP)、Next-Key锁(GAP+行锁)等。

查看与设置隔离级别

查看全局隔离级别:

SELECT @@global.tx_isolation;

查看当前会话全局隔离级别:

SELECT @@session.tx_isolation;

查看隔离级别(默认同上):

SELECT @@tx_isolation;

设置当前会话或全局隔离级别:

SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERALIZABLE}

读未提交(Read Uncommitted)

设置全局隔离级别为读未提交,在终端A开启事务并更新指定行,在终端A提交事务前,在终端B看到了未commit的数据(即脏读)。

终端A:

终端B:

终端B的事务执行过程中读到了终端A更新但未commit的数据,属于脏读。

读提交(Read Committed)

设置全局隔离级别为读提交,在终端A开启事务并更新数据,在终端B开启事务并在终端A事务提交前后读取同一数据读到了不同的数据,即发生了不可重复读的现象。

终端A:

终端B:

在同一事务内,在不同时间段,读取到了不同的值,即不可重复读。

可重复读(Repeatable Read)

设置全局隔离级别为可重复读,在终端A开启事务,并更新数据,在终端B开启事务,并在终端A事务提交前后读取同一数据读到了相同的数据,即可重复读。将更新数据改成插入数据,再实验一次,发现没有发生读取数据也是读到了相同的数据,即没有发生幻读。

注意:设置隔离级别后要退出mysql客户端重新启动。

终端A:

终端B:

可见在终端A的事务提交前后,在终端B的事务中读取到的数据都是一致的,也即具有可重复读的性质。

倘若将update数据变成插入数据会如何:

终端A:

终端B:

可见还是满足了可重复读,没有出现幻读现象,这是因为MySQL在RR级别的时候,是解决了幻读问题的(解决的方式是用Next-Key锁 (GAP+行锁)解决的)。

注意:Next-Key锁 就是间隙锁和记录锁的组合。(源自MySQL官网)

串行化(Serializable)

设置全局隔离级别为串行化,其中终端A更新数据后,在终端B开启事务,并读取数据,发现阻塞住了,当终端A事务提交后,终端B才看到数据。

终端A:

终端B:

隔离级别总结

总结:

较低的隔离级别可以提高并发性能,但可能会引入数据一致性问题;而较高的隔离级别可以保证数据一致性,但可能降低并发性能。

不可重复读关注的是修改和删除:读取过的数据,再次读取处理就发现值不一样了。

幻读的重点在于新增:同样的条件,第一次和第二次读出来的记录数不一样。

MySQL默认的隔离级别是可重复读,也是兼顾数据一致性和并发性考虑的较优选择。

一致性的理解

事务在执行过程中,将数据库从一个一致的状态转换为另一个一致的状态。

一致性和用户业务逻辑强相关,MySQL提供技术支持,一致性还需要用户业务逻辑做支撑,也就是一致性由用户决定。技术上,有原子性、隔离性、持续性保证一致性。

多版本并发控制

数据库的并发场景有三种:

读-读:没有问题,不用并发控制。

读-写:有线程安全问题,可能会造成事务隔离性问题,即可能遇到脏读、幻读、不可重复读。

写-写:有线程安全问题,可能会存在更新丢失问题。

数据库的更新丢失问题是指在并发环境下,多个事务同时对同一数据进行更新操作时可能出现的问题。 

MVCC(Multi-Version Concurrency Control):多版本并发控制       

多版本并发控制是一种用来解决读 - 写冲突的无锁并发控制。

为事务分配单向增长的事务ID,为每个修改保存一个版本,版本与事务ID关联,读操作只读该事务开始前的数据库的快照,所以MVCC可以为数据库解决下面问题:

并发读写时,读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能。

解决脏读、幻读、不可重复读等事务隔离问题,但不能解决更新丢失问题。

记录中的3个隐藏字段

DB_TRX_ID:6byte,最近修改事务ID,记录创建这条记录/最后一次修改该记录的事务ID。

DB_ROLL_PTR:7byte,回滚指针,指向这条记录的上一个版本

DB_ROW_ID:6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引(数据行按照索引的顺序存储在磁盘上,具有相邻的物理位置)。

还有一个flag隐藏字段,记录这数据被更新或删除,记录删除只需标记flag即可。

undo日志

MySQL中的一段内存缓存区,用来保存日志数据。

画图理解MVCC

假设表中刚插入了一条记录如下,因为不知道创建该记录的事务ID,隐式主键,首条记录也没有回滚指针,所以将他们设置成默认值:

现在执行事务1,对表记录进行修改,修改张三名字为李四。

修改记录前,对记录加行锁,将原本记录拷贝到undo log中,然后写入新的数据,事务ID为1,回滚指针指向副本记录,表示新记录的上个版本就是它。

然后提交事务,释放锁。

事务1执行后:

现在再执行事务2,对表中记录进行修改,将age改成19.

修改记录前,对记录加行锁,将原本记录拷贝并头插入undo log中。

然后修改记录,事务ID为2,回滚指针指向上一个版本的记录。

提交事务2,释放锁。

事务2执行后:

于是我们就有了一个基于链表记录的历史版本链,回滚就是用版本链中的版本覆盖当前数据。

这个历史版本链不仅用于数据回滚,还用于其他事务读取历史版本,所以只有当前记录提交且其他事务与版本链无关的时候,该历史版本链才能删除。

插入和删除的时候如何维护版本链?

删除:将数据拷贝一份放入undo log中,并将该记录的隐藏flag删除字段置为1,这样回滚后flag字段又变成0,相当于删除的数据又恢复了。

插入:新插入的数据没有历史版本,为了回滚操作,新插入的数据拷贝一份放到undo log中,且undo log中的记录的flag标记为1,回滚后就相当于数据被删除了。

上面的一个个版本,我们可以称之为一个个快照。

概念:

快照读:读取历史版本的数据。

当前读:读取当前版本的数据。

事务对数据进行增删改的时候,操作的都是最新记录,即当前读,需要进行加锁保护。
事务在进行select查询的时候,既可能是当前读也可能是快照读,如果是当前读,那也需要进行加锁保护,但如果是快照读,那就不需要加锁,因为历史版本不会被修改,也就是可以并发执行,提高了效率,这也就是MVCC的意义所在。
 

Read View

Read View就是事务进行快照读操作时产生的读视图(Read View)。

在该事务执行的快照读的那一刻,会生成数据库系统当前的快照,记录并维护系统当前活跃事务的ID。(每个事务开启,都分配一个递增的ID,越新的事务,事务ID越大)

Read View在MySQL源码中是一个类,用来可见性判断的,判断当前事务能看到哪个版本的数据。

其基本组成如下:

class ReadView {
// 省略...
private:
/** 高水位,大于等于这个ID的事务均不可见*/
trx_id_t m_low_limit_id;
/** 低水位:小于这个ID的事务均可见 */
trx_id_t m_up_limit_id;
/** 创建该 Read View 的事务ID*/
trx_id_t m_creator_trx_id;
/** 创建视图时的活跃事务id列表*/
ids_t m_ids;
/** 配合purge,标识该视图不需要小于m_low_limit_no的UNDO LOG,
* 如果其他视图也不需要,则可以删除小于m_low_limit_no的UNDO LOG*/
trx_id_t m_low_limit_no;
/** 标记视图是否被关闭*/
bool m_closed;
// 省略...
};

那么MVCC是如何实现的?

结合源码和画图理解:

画图理解:其中id就是下图的DB_TRX_ID

当进行快照读的时候就对比DB_TRX_ID和Read View中的水位线id即可。

当DB_TRX_ID即id等于创建该快照的id或小于up_limit_id那么该记录是可见的。(小于up_limit_id表示事务已提交)。

当id>=low_limit_id说明事务是快照生成后才提交的事务,则事务是不可见的。

当事务id在活跃事务id列表中时,表示事务在进行,也是不可见的。
 

RR与RC的本质区别

RR是可重复读,RC是读提交,前面提到,读提交是可以读到已经提交的事务的,是不可重复读的,而可重复读的隔离级别是能在一个事务中保证读取数据的一致性的。

RR和RC级别的不同,本质是Read View生成的时机不同。

在RR级别下某个事务对某条记录的第一次快照读会创建一个快照及Read View,将当前系统活跃的其他事务记录起来,此后再进行快照读的时候,使用的是同一个Read View,所以对之后的修改不可见,实现了读取数据的可重复读。

而在RC级别下,在事务中每一次快照读都会生成并获取最新的Read View,所以别的事务提交了,新的快照读也就马上能看到,所以RC具有不可重复读的问题。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值