【MySQL事务】1-MySQL中事务的简单理解

目录

1、事务的四大特性:ACID

2、举个简单的例子理解以上ACID四大特性

3、多事务并发导致的数据一致性问题:共三种

4、事务的四种隔离级别:RU/RC/RR/S

4.1 MySQL数据库中的事务再讲解

4.2 MySQL数据库事务隔离级别查询与设置

4.3 测试事务隔离级别之 读未提交RU:read uncommitted

4.4 测试事务隔离级别之 读已提交RC:read committed

4.5 测试事务隔离级别之 可重复读RR:repeatable read

4.6 测试事务隔离级别之 串行化S:serializable

99、面试题:说说你对数据库事务的理解?

1、事务的四大特性:ACID

谈到事务的四大基本特性,一般都是以下四点:

(1)原子性(Atomicity):原子性是指事务是一个不可分割的工作单位,事务中的一组操作要么都发生,要么都不发生。
(2)一致性(Consistency):事务执行前后 数据的完整性必须保持一致。
(3)隔离性(Isolation):事务的隔离性是指多个用户并发访问数据库时,数据库为一个用户开启的事务,不能被其他事务的数据操作所干扰,多个并发事务之间要相互隔离。
(4)持久性(Durability):持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。

2、举个简单的例子理解以上ACID四大特性

(1)原子性(Atomicity):事务中的一组操作要么都发生,要么都不发生。

A有800元,B有200元,A转账200元给B,

这个转账过程包含两个步骤:A减少200元: 800 - 200 = 600;B增加200元:   200 + 200 = 400 。

原子性表示,这两个步骤要么都执行完成,或者要么都不执行,不能只发生其中一个动作。

(2)一致性(Consistency):针对一个事务操作前与操作后的数据状态要保持一致。

操作前 A:800,B:200
操作后 A:600,B:400

一致性表示,一个事务完成后,要符合数据的逻辑运算,操作前和操作后的数据总量都是1000元。

(3)持久性(Durability):表示事务结束后,事务中改变的数据不随着外界原因而导致数据丢失。

1.1)操作前 A:800,B:200

1.2)如果在操作前(事务还没有提交)服务器宕机或者断电,那么重启数据库以后,数据状态应该为 A:800,B:200

2.1)操作后 A:600,B:400

2.2)如果在操作后(事务已经提交)服务器宕机或者断电,那么重启数据库以后,数据状态应该为 A:600,B:400

(4)隔离性(Isolation):针对多个用户的并发数据库操作,主要是排除其他事务对本次事务的影响。

事务1:A有800元,B有200元,A转200元给B。事务2:C有1000元,B有200元,C转100元给B。

两个事务同时进行,其中一个事务读取到了另外一个事务还没有提交的数据,导致了在事务2中B的余额是400元。

3、多事务并发导致的数据一致性问题:共三种

问题引入:多个用户对数据库表执行并发操作时,有可能会带来数据一致性问题。同时,事务的隔离性(Isolation)是有级别的,不同的隔离级别会解决不同的问题。
(1)脏读:一个事务读取到了另外一个事务还没有提交的数据,导致在前一个事务中引用了错误的数据(200元)。

(2)不可重复读:在同一个事务中,重复读取表中的某个数据,发现前后两次读取到的数据不一致(数据被更新了或者该条数据记录被删除了,是update操作或者delete操作)。在同一个事务内,读取表中的某一行数据,多次读取 结果却不同(这个不一定是错误,只是某些场合不对)。
比如:在页面上统计查询的时候,发现 A是100元、B是200元、C是500元,如下:

但是,在点击生成账户报表的同时,有人给B转账进来300元,而且事务已经提交,此时下载出来的数据是:A是100元、B是500元、C是500元,如下:

两次查询发现B账户的余额发生变化了,这就是不可重复读。

(3)幻读:在一个事务中,读取到了另外一个事务已经插入的数据,导致在前一个事务中前后两次读出来的结果不一致(记录条数增加了,是insert操作)。是指在一个事务中读取到了别的事务已经插入的数据,导致前后读取不一致(一般是记录的条数影响,多了新的记录行)。

4、事务的四种隔离级别:RU/RC/RR/S

4.1 MySQL数据库中的事务再讲解

我们在开发数据库应用时,会遇到这样的场景:

假设有个表 users(id pk, name, age, sex) 用于存放用户信息,有个表 blogs(id pk, user_id fk, title, content) 用于存放用户记录的博客信息,blogs 的 user_id 字段记录博客拥有者的id。现在,我们需要删除某个用户,当然我们也会连同删掉其的博客信息。

delete from users where id = 100;
delete from blogs where user_id = 100;

我们可能会遇到删除出错的情况:删除了用户信息,但是删除其博客信息出错,那么这些博客就成了无主的博客了,这是我们不允许的!所以,我们希望这2个sql语句的执行过程符合:要么全做,要么全不做。数据库事务 Transaction 就是解决这个问题的利器(当然有人会说, 使用级联删除,这个以后再讲)。

什么是事务?事务是指 作为一个逻辑工作单元的一系列SQL操作,要么全都执行,要么全都不执行。

另外,一个逻辑工作单元要成为事务,必须满足ACID属性:

(1)A->Atomicity,原子性:事务必须是原子工作单元,整个事务中的所有操作,要么全都执行,要么全都不执行,不可能停滞在中间某个环节。

(2)C->Consistency,一致性:事务必须始终保证系统数据处于一致的状态,不管在任何给定的时间并发事务有多少。

(3)I->Isolation,隔离性:由并发事务所做的修改必须与任何其他并发事务所作的修改相互隔离,事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一并发事务修改它之后的状态,事务不能查看中间状态的数据。当事务可序列化时(即串行化)可以获得最高的隔离级别,但是,势必会降低系统处理事务的吞吐量。

(4)D->Durability,持久性:事务提交之后,它对于系统所作的修改应该是持久性的,即使系统出现故障也将一直保持。

在MySQL中InnoDB存储引擎是支持事务的,MyISAM存储引擎则不支持,以下针对InnoDB而言:Mysql默认情况下,是自动提交事务的,也即 每当我们执行一个查询都会被当做一个事务自动执行,针对最上面的场景我们可以通过MySQL数据库事务来解决:

查询MySQL数据库是否开启自动提交:show variables like "%autocommit%";

手动关闭自动提交(session级别),使用start transaction 显示开启一个事务,然后执行单个逻辑工作单元,最后使用commit提交,或者rollback回滚。

set autocommit=0;
start transaction;
delete from users where id = 100;
delete from blogs where user_id = 100;
commit;

4.2 MySQL数据库事务隔离级别查询与设置

(1)事务关键字帮助查询:help isolation;

#设置数据库隔离级别的语法
SET TRANSACTION ISOLATION LEVEL
{
     REPEATABLE READ
   | READ COMMITTED
   | READ UNCOMMITTED
   | SERIALIZABLE
}

命令小计:

show variables like 'transaction_isolation';                #查询隔离级别
set session transaction isolation level read uncommitted;   #更改隔离级别
set session transaction isolation level read committed;
set session transaction isolation level repeatable read;
set session transaction isolation level serializable;

(2)查询当前事务隔离级别:show variables like '%isolation%';

(3)查看当前会话的隔离级别:select @@transaction_isolation;

(4)查看当前系统的隔离级别:select @@global.transaction_isolation;

(5)设置会话级别的隔离级别,隔离级别由低到高设置依次为(当前窗口即可生效):

select @@transaction_isolation, @@global.transaction_isolation;
set session transaction isolation level read uncommitted;
set session transaction isolation level read committed;
set session transaction isolation level repeatable read;
set session transaction isolation level serializable;

(6)设置系统级别的隔离级别,隔离级别由低到高设置依次为(MySQL重启之后才能生效):

select @@transaction_isolation, @@global.transaction_isolation;
set global transaction isolation level read uncommitted;
set global transaction isolation level read committed;
set global transaction isolation level repeatable read;
set global transaction isolation level serializable;

(7)注意:MySQL默认的事务处理级别是 REPEATABLE-READ ,而Oracle和SQL Server是 READ_COMMITED。

(8)4种隔离级别的含义如下:

设置描述
RU 级别:read uncommitted(读未提交):最低级别,脏读、不可重复读、幻读 均可能发生。
RC 级别:read committed(读已提交):可避免脏读情况的发生。
RR 级别:repeatable read (可重复读):可避免脏读、不可重复读情况的发生。
S 级别:serializable  (串行化):可避免脏读、不可重复读、幻读情况的发生。

 


 

 

 

 

【问题】在cmd界面登录mysql,查询事务隔离级别的时候,报错:未知系统变量 'tx_isolation'

【解决办法】由于mysql数据库的更新,在旧版本中tx_isolation是作为transaction_isolation的别名被应用的,新版本已经弃用了,所以输入会显示未知变量。把tx_isolation换成transaction_isolation,就可以显示结果。

4.3 测试事务隔离级别之 读未提交RU:read uncommitted

前面我们说过,要获得最高的事务隔离级别,可以采取串行化 serializable 的方式,但是,其代价就是严重影响系统处理事务的吞吐量。就好像数据库是个多核CPU,事务串行化后,那么意味着我们总是在使用单核,没办法发挥多核CPU的并行威力。基于以上这个问题,大多数的数据库实现都为使用者提供了多个事务隔离级别。

MySQL提供的事务隔离级别有: Read Uncommitted,Read Committed,Repeatable Read,Serializable,隔离级别依次递增。

MySQL默认的事务隔离级别是 Repeatable Read。

现在学习Read Uncommitted这种数据库隔离级别:Read Uncommitted,顾名思义,就是读未提交,也就是说:事务所作的修改在未提交前,其他并发事务是可以读到的。

下面我们来演示一下这个过程:

(1)假设,现在有个学生小明连接到数据库去读取自己本学期的成绩,它设置session(当前会话连接)的事务隔离级别为 读未提交 RU:Read Uncommitted:


xiaoming> select @@transaction_isolation;
+-----------------+
| @@transaction_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set (0.00 sec)
 
xiaoming> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
 
xiaoming> select @@transaction_isolation;
+------------------+
| @@transaction_isolation   |
+------------------+
| READ-UNCOMMITTED |
+------------------+

(2)就在这个时候,小明的班主任王老师也连接了数据库去登记学生本学期的成绩:

mr.wang> start transaction;
Query OK, 0 rows affected (0.00 sec)

mr.wang> insert into scores(name, score) values ("xiaoming", 59);
Query OK, 1 row affected (0.00 sec)

(3)当王老师还没有提交事务时,小明刚好开始查询自己的成绩,结果他查到自己考了59分,他伤心的要死:

xiaoming> start transaction;
Query OK, 0 rows affected (0.00 sec)
 
xiaoming> select * from scores where name = 'xiaoming';
+----+----------+-------+
| id | name     | score |
+----+----------+-------+
|  1 | xiaoming |    59 |
+----+----------+-------+
1 row in set (0.00 sec)

(4)就在小明查成绩之后,王老师发现自己登错了成绩,其实小明考了69分,于是 王老师回滚了当前事务, 并重新录入了小明的正确成绩,并提交了

mr.wang> rollback;
Query OK, 0 rows affected (0.00 sec)
 
mr.wang> start transaction;
Query OK, 0 rows affected (0.00 sec)
 
mr.wang> insert into scores(name, score) values ("xiaoming", 69);
Query OK, 1 row affected (0.00 sec)
 
mr.wang> commit;
Query OK, 0 rows affected (0.00 sec)

(5)小明刚才查完成绩之后,也没有复查成绩,因此整个寒假都过的很不开心,毕竟自己"没有及格"!

通过上述场景我们发现:Read Uncommitted 这个最低的事务隔离级别存在以下这些问题:出现脏读(dirty reads) 问题,就像上面王老师录入的错误成绩(脏数据)被小明读到一样。

4.4 测试事务隔离级别之 读已提交RC:read committed

这篇我们学习事务隔离级别 Read Committed,顾名思义,就是读已提交,一个事务只能看到其他并发的已提交事务所作的修改,未提交事务所做的修改是看不到的。很显然,该隔离级别可以解决Read Uncommitted 中出现的“脏读“问题。除了MySQL,很多数据库都以Read Committed作为默认的事务隔离级别,比如 SQL server、Oracle等。

下面我们来演示一下这个过程:通过例子来演示Read Committed解决 “脏读” 问题:

(1)小明连接数据库去查询自己本学期的成绩,他设置session(当前会话连接)的事务隔离级别为Read Committed

xiaoming> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)
 
xiaoming> select @@transaction_isolation;
+----------------+
| @@transaction_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
1 row in set (0.00 sec)

(2)就在这个时候,小明的班主任王老师也连接了数据库去登记学生本学期的成绩,并且还没提交该事务

mr.wang> start transaction;  
Query OK, 0 rows affected (0.00 sec)  
  
mr.wang> insert into scores(name, score) values ("xiaoming", 59);  
Query OK, 1 row affected (0.00 sec)

(3)此时王老师还没有提交刚才的事务,然后小明刚好开始查询自己的成绩,结果他没有查到成绩,因为王老师还没有提交(也即 在小明的事务中无法读取另外一个事务未提交的数据):

xiaoming> start transaction;
Query OK, 0 rows affected (0.00 sec)
 
xiaoming> select * from scores where name = 'xiaoming';  
Empty set (0.00 sec)

(4)小明查成绩之后(没有查出来数据),王老师发现自己登错了成绩,其实小明考了69分,于是王老师回滚了当前事务, 并重新录入了小明的正确成绩,并提交了:

mr.wang> rollback;
Query OK, 0 rows affected (0.00 sec)
 
mr.wang> start transaction;  
Query OK, 0 rows affected (0.00 sec)
 
mr.wang> insert into scores(name, score) values ("xiaoming", 69); 
Query OK, 1 row affected (0.00 sec)
 
mr.wang> commit;
Query OK, 0 rows affected (0.00 sec)

(5)在这时,接着小明又半信半疑的查了一次成绩,这次他查到了(也即 在小明的事务中可以读取到另外一个事务已经提交的数据),他很开心,因为他及格了。

xiaoming> select * from scores where name = 'xiaoming';  
+----+----------+-------+
| id | name     | score |
+----+----------+-------+
|  1 | xiaoming |    69 |
+----+----------+-------+
1 row in set (0.00 sec)

由此可见,Read Committed 可以解决 “脏读”问题

但是 Read Committed 不能保证在同一个事务中,每次读都能读到相同的数据,因为在每次读数据之后其他并发事务可能会对刚才读到的数据进行修改update或者删除delete,这也反映出 Read Committed 事务隔离级别仍然存在以下问题:“不可重复读”问题, 也即在一个事务范围内,前后两次相同的查询却返回了不同的数据。

4.5 测试事务隔离级别之 可重复读RR:repeatable read

接下来我们学习MySQL默认的事务隔离级别Repeatable Read,顾名思义,可重复读,也即在一个事务范围内前后多次相同的查询都会返回相同的数据。

下面我们来演示一下这个过程:延续上面的例子:

(1)小明第一次查询时发现自己考了69分,很开心,于是他连接到数据库查询自己的成绩来炫耀给小伙伴,由于Repeatable Read是默认的事务隔离级别,因此这次他不需要进行修改MySQL的事务级别:

xiaoming> select @@transaction_isolation;
+-----------------+
| @@transaction_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set (0.00 sec)
 
xiaoming> start transaction;
Query OK, 0 rows affected (0.00 sec)
 
xiaoming> select * from scores where name = 'xiaoming';
+----+----------+-------+
| id | name     | score |
+----+----------+-------+
|  1 | xiaoming |    69 |
+----+----------+-------+
1 row in set (0.00 sec)

(2)不幸的是,小明的班主任王老师复查试卷后,发现小明的成绩多加了10分,于是他连接到数据库来修改小明的成绩到59分,并且提交事务:

mr.wang> start transaction;
Query OK, 0 rows affected (0.00 sec)
 
mr.wang> update scores set score = 59 where name = 'xiaoming';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
 
mr.wang> commit;
Query OK, 0 rows affected (0.00 sec)

(3)接着,小明觉得还不尽兴,于是又查一次,还是69分(在小明的事务中无法读取其他事务已经提交的数据,其他事务对小明的事务无干扰),可怜的是他目前还不知道自己其实是不及格的:

xiaoming> select * from scores where name = 'xiaoming';
+----+----------+-------+
| id | name     | score |
+----+----------+-------+
|  1 | xiaoming |    69 |
+----+----------+-------+
1 row in set (0.00 sec)

由此可见,Repeatable Read 的确可以解决“不可重复读”的问题,小明在一次事务中做了前后两次相同的查询,其查询结果都是一样的,也即成绩都是69分,即使两次查询过程中王老师修改了其成绩并且提交事务了。

请注意 我们演示的场景中,王老师是针对一条已有的记录进行了 update 操作。 如果王老师是新增 insert 小明的成绩,那么小明的前后两次查询的结果还是不一样的,如下所示:
(1)首先小明第一次查询, 没有成绩:

xiaoming> select * from scores where name = 'xiaoming';
Empty set (0.00 sec)

(2)然后王老师录入成绩insert操作,并且提交事务了:

mr.wang> start transaction;
Query OK, 0 rows affected (0.00 sec)
 
mr.wang> insert into scores(name, score) values ("xiaoming", 59);
Query OK, 1 row affected (0.00 sec)
 
mr.wang> commit;
Query OK, 0 rows affected (0.00 sec)

(3)最后,小明再次查询成绩,这次有成绩了:

xiaoming> select * from scores where name = 'xiaoming';
+----+----------+-------+
| id | name     | score |
+----+----------+-------+
|  1 | xiaoming |    59 |
+----+----------+-------+
1 row in set (0.00 sec)

通过上述例子,我们可以看出Repeatable Read也是存在以下问题的:“幻读”问题,也即在一次事务范围内,前后进行多次相同的查询,如果有其他并发事务中途 insert 插入了新的记录,那么在前一个事务中的之后的多次查询都会读取到这些“幻影”行,也即新增的行记录。另外,我们也需要注意,“不可重复读” 对应的是修改或删除 即Update或delete操作,“幻读”对应的是插入 即Insert操作。

4.6 测试事务隔离级别之 串行化S:serializable

最后我们学习一下最高的事务隔离级别Serializable,顾名思义,串行化,也即并发事务串行执行。很显然,该隔离级别可以避免前面讲到的所有问题:“脏读”、“不可重复读”和“幻读”。代价是处理事务的吞吐量大幅度降低,严重浪费数据库的性能,因此要慎用此事务隔离级别。

下面演示Serializable如何解决这些问题:

(1)小明连接数据库去查询自己本学期的成绩,他设置session(当前连接)的事务隔离级别为Serializable:

xiaoming> set session transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)
 
xiaoming> select @@transaction_isolation;
+----------------+
| @@transaction_isolation |
+----------------+
| SERIALIZABLE   |
+----------------+
1 row in set (0.00 sec)

(2)小明开始查询成绩,由于王老师还没有录入,因此没有查询到成绩:

xiaoming> start transaction;
Query OK, 0 rows affected (0.00 sec)
 
xiaoming select * from scores where name = 'xiaoming';
Empty set (0.00 sec)

(3)这时,小明的班主任王老师也连接数据库来录入成绩,可是他会卡在插入第一条成绩信息这里, 如下所示,insert 语句迟迟不会返回

mr.wang> start transaction;
Query OK, 0 rows affected (0.00 sec)
 
mr.wang> insert into scores(name, score) values ('xiaoming', 69);

(4)小明结束本次查询事务:

xiaoming> commit;
Query OK, 0 rows affected (0.00 sec)

(5)这时,王老师的事务中 刚才插入的第一条成绩数据才执行完成

mr.wang> insert into scores(name, score) values ('xiaoming', 69);
Query OK, 1 row affected (3.42 sec)

(6)如果小明久久不commit自己事务中的查询,还会导致王老师录入成绩超时:

xiaoming> insert into scores(name, score) values ('xiaoming', 69);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

从上面的例子我们可以看出,如果一个session设置隔离级别为Serializable时,其执行事务时会阻塞其他并发的事务,从上面的错误信息中 我们也可以看出应该是通过表锁来实现的。既然是这样,那么“脏读”、“不可重复读”和“幻读”自然是不可能发生了。

 

99、面试题:说说你对数据库事务的理解?

在回答这个问题的时候,首先要考虑的是这里面包含了哪些知识点是我们要回答的,
第一个要点:什么是事务?
第二个要点:事务的基本特性是什么?
第三个要点:什么是事务隔离?有哪些事务隔离级别?

一、什么是事务
数据库中的事务是作为单个逻辑工作单元执行的一系列操作。(多条 SQL 语句,要么全部都执行成功,要么全部都不执行。)

二、事务的基本特性是什么?

数据库要支持事务操作必须满足四个特性,也就是常说的ACID:
A:原子性(Atomicity):原子性是指事务包含的所有操作要么全部成功,要么全部失败
C:一致性(Consistency):一个事务执行之前和执行之后都必须处于一致性状态。
I:隔离性(Isolation):多个事务在执行同一个操作时不能被其他事务干扰。
D:持久性(dependency):一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。
三、什么是事务隔离,有哪些事务隔离级别?
事务的隔离性就是指,多个并发的事务同时访问一个数据库时,一个事务不应该被另一个事务所干扰,每个并发的事务间要相互进行隔离。

一般的数据库,都包括以下四种隔离级别:读未提交(Read Uncommitted)、读提交(Read Committed)、可重复读(Repeated Read)、串行化(Serializable)。

读未提交(Read Uncommitted):读未提交,就是可以读到未提交的内容。因此,在这种隔离级别下,查询是不会加锁的,也由于查询的不加锁,所以这种隔离级别的一致性是最差的,可能会产生“脏读”、“不可重复读”、“幻读”。

读已提交(Read Committed):读已提交,就是读到已经提交了的内容。这是各种系统中最常用的一种隔离级别,也是SQL Server和Oracle的默认隔离级别。这种隔离级别能够有效的避免脏读,但除非在查询中显示的加锁,否则 “读已提交”只能避免“脏读”,并不能避免“不可重复读”和“幻读”。

可重复读(Repeated Read):可重复读,就是专门针对“不可重复读”这种问题而制定的隔离级别,它可以有效的避免“不可重复读”,并且它也是MySql的默认隔离级别。当事务启动时,不允许进行“修改操作(Update)或者删除操作(Delete)”,而“不可重复读”恰恰是因为两次读取之间进行了数据的修改,因此,“可重复读” 能够有效的避免“不可重复读”,但却避免不了“幻读”问题,因为幻读是由于“插入操作(Insert)”而产生的。

串行化(Serializable):数据库最高的隔离级别,这种级别下,事务“串行化顺序执行”,也就是一个一个排队执行(每一行数据都进行了加锁)。这种级别下,“脏读”、“不可重复读”、“幻读”都不会出现,但是执行效率非常差,性能开销也最大,所以基本没人会用。
 

======================================================================

上面是一位牛人写得关于数据库事务基础知识的文字,因为很好,不敢修饰,转载至此与博友共享。原文博客:https://blog.csdn.net/dengjili/article/details/82468576

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值