MySQL:事务的理解

一、CURD不加控制,会有什么问题

(1)因为,MySQL里面存的是数据,所以很有可能会被多个客户访问,所以mysqld可能一次会接受到多个关于CURD的请求。(2)且mysql内部是采用多线程来完成数据存储等相关工作的,所以必然会存在对数据并发访问的场景 ------>会导致一些多请求并发可能产生的异常结果

比如同行转账,按道理是我减100,你加100,但是因为我是同行所以用的是一张数据库的表,可能我减100的时候还没做完网络或者数据库出问题等其他原因导致没有给你加100,那么整个操作就会出现一个中间过程(我减了但是你没有加),这就有问题,在这种情况下我们允许异常产生,一旦操作没有完成我们应该把减掉的100再加回来,就好像什么都没做,等待下次合适的时候再去转账。这就相当于转账之后不要有中间过程,而是在转的时候一旦出现异常就直接进行回滚,因为不回滚的话就会有问题,必须得回滚保证和初始的状态一样,这就叫我们的回滚操作。在高并发的场景下数据或多或少都会出现这样的问题,所以这也就要求mysql必须要有针对这类问题的解决方案。

二、CURD满足什么属性,能解决上述问题?

  1. 买票的过程得是原子的吧(要么不抢,要么抢到,出现中间状态会回滚)

  2. 买票互相应该不能影响吧(我买的时候你正好过来,我的行为不能影响你,也就是彼此之间得是割裂的)

  3. 买完票应该要永久有效吧 ( 购买成功这个情况必须得做持久化 )

  4. 买前,和买后都要是确定的状态吧(买前就是没买,买后就是买了,不允许有不确定的状态)

三、什么是事务?

事务就是一组DML语句组成,这些语句在逻辑上存在相关性(单独一条是没有意义的,比如转账就应该至少有两条sql语句,即我减100,你加100,整体在一起才有转账逻辑,所以事务一定要站在mysql的上层去看待sql语句,具体完成一个由多条sql语句构成的应用层功能,在业务上有具体含义的动作),这一组DML语句要么全部成功,要么全部失败,是一个整体。MySQL提供一种机制,保证我们达到这样的效果。事务还规定不同的客户端看到的数据是不相同的。

**事务就是要做的或所做的事情,主要用于处理操作量大,复杂度高的数据。**假设一种场景:你毕业了,学校的教务系统后台 MySQL 中,不再需要你的数据,要删除你的所有信息(一般不会:) ), 那么要删除你的基本信息(姓名,电话,籍 贯等)的同时,也删除和你有关的其他信息,比如:你的各科成绩,你在校表现,甚至你在论坛发过的文章等。这样,就需要多条 MySQL 语句构成,那么所有这些操作合起来,就构成了一个事务。

正如我们上面所说,**一个 MySQL 数据库,可不止你一个事务在运行,同一时刻,甚至有大量的请求被包装成事务, 在向 MySQL 服务器发起事务处理请求。而每条事务至少一条 SQL ,最多很多 SQL ,这样如果大家都访问同样的表数据,在不加保护的情况,就绝对会出现问题。**甚至,因为事务由多条 SQL 构成,那么,也会存在执行到一半出错或者 不想再执行的情况,那么已经执行的怎么办呢 ?

**所以,一个完整的事务,绝对不是简单的sql集合,**还需要满足如下四个属性:

1、原子性 :**一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。**事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过 一样。

2、一致性 :**在事务开始之前和事务结束以后,数据库的完整性没有被破坏。(一种状态变为另一种状态结果是可预期的)**这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。

3、隔离性 :**数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。**事务隔离分为不同级别,包括读未提交( Read uncommitted )、读提交 ( read committed )、可重复读( repeatable read )和串行化( Serializable )

4、持久性事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

mysql从技术上只要保证了134,就可以做到2,所以134是因 2是果(还需要上层用户配合)

上面四个属性,可以简称为 ACID 。

原子性(Atomicity,或称不可分割性)

一致性(Consistency)

隔离性(Isolation,又称独立性)

持久性(Durability)

mysql需要帮不同的客户端处理不同的事务请求,所以运行期间在自身内部必然存在大量的事务,所以他必须得将事务按照先描述后组织的形式管理起来,所以mysql会把这些事务打包描述成对象,然后放入到事务执行列表里,并帮我们解决一系列执行事务时可能出现的问题

四、为什么会有事务

事务被 MySQL 编写者设计出来(不是天然就有的,而是使用过程中发现应用层需要才被设计出来的),本质是为了当应用程序访问数据库的时候,事务能够简化我们的编程模型,不需要我们去考虑各种各样的潜在错误和并发问题.(你只需要说你的需求,其他的我帮你处理)可以想一下当我们使用事务时,要么提交,要么回滚,我们不会去考虑网络异常了,服务器宕机了,同时更改一个数据怎么办对吧?因此事务本质上是为了应用层服务的.而不是伴随着数据库系统天生就有的.

备注:我们后面把 MySQL 中的一行信息,称为一行记录

五、事务的版本支持

在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务, MyISAM 不支持。

查看数据库引擎 :

sql 复制代码
mysql> show engines \G         -- 行显示
*************************** 1. row ***************************
     Engine: InnoDB    -- 引擎名称
     Support: DEFAULT   -- 默认引擎
     Comment: Supports transactions, row-level locking, and foreign keys--支持事务、行级锁,外键
Transactions: YES       -- 支持事务
         XA: YES
 Savepoints: YES       -- 支持事务保存点
*************************** 2. row ***************************
     Engine: MRG_MYISAM
     Support: YES
     Comment: Collection of identical MyISAM tables
Transactions: NO
         XA: NO
 Savepoints: NO
*************************** 3. row ***************************
     Engine: MEMORY    --内存引擎
     Support: YES
     Comment: Hash based, stored in memory, useful for temporary tables
Transactions: NO
         XA: NO
Savepoints: NO
*************************** 4. row ***************************
     Engine: BLACKHOLE
     Support: YES
     Comment: /dev/null storage engine (anything you write to it disappears)
Transactions: NO
         XA: NO
 Savepoints: NO
*************************** 5. row ***************************
     Engine: MyISAM    
     Support: YES
     Comment: MyISAM storage engine
Transactions: NO           -- MyISAM不支持事务
         XA: NO
 Savepoints: NO
*************************** 6. row ***************************
     Engine: CSV
     Support: YES
     Comment: CSV storage engine
Transactions: NO
         XA: NO
 Savepoints: NO
*************************** 7. row ***************************
     Engine: ARCHIVE
     Support: YES
     Comment: Archive storage engine
Transactions: NO
         XA: NO
 Savepoints: NO
*************************** 8. row ***************************
     Engine: PERFORMANCE_SCHEMA
     Support: YES
     Comment: Performance Schema
Transactions: NO
         XA: NO
 Savepoints: NO
*************************** 9. row ***************************
     Engine: FEDERATED
     Support: NO
     Comment: Federated MySQL storage engine
Transactions: NULL
         XA: NULL
 Savepoints: NULL
9 rows in set (0.00 sec)

六、事务的提交方式

事务的提交方式常见的有两种: 自动提交、手动提交

查看事务提交方式 :

sql 复制代码
show variables like 'autocommit';

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

sql 复制代码
SET AUTOCOMMIT=0;            #SET AUTOCOMMIT=0 禁止自动提交
sql 复制代码
mysql> SET AUTOCOMMIT=1;           #SET AUTOCOMMIT=1 开启自动提交

七、事务常见操作方式

简单银行用户表

sql 复制代码
## Centos 7 云服务器,默认开启3306 mysqld服务
netstat -nltp

为了便于演示,我们将mysql的默认隔离级别设置成读未提交

sql 复制代码
set global transaction isolation level READ UNCOMMITTED;

设置了却没有用 ,因为需要重启终端才可以

sql 复制代码
select @@tx_isolation;

创建测试表

sql 复制代码
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;

7.1 正常演示 - 证明事务的开始与(定向)回滚

sql 复制代码
mysql> show variables like 'autocommit';  -- 查看事务是否自动提交。我们故意设置成自动提交,看看该选项是否影响begin   从这一行往后所有的语句都属于这个事务

+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit   | ON   |
+---------------+-------+
1 row in set (0.00 sec)
mysql> start transaction;               -- 开始一个事务begin也可以,推荐begin

Query OK, 0 rows affected (0.00 sec)
mysql> savepoint save1;                -- 创建一个保存点save1(根据需求设置保存点)

Query OK, 0 rows affected (0.00 sec)
mysql> insert into account values (1, '张三', 100);   -- 插入一条记录

Query OK, 1 row affected (0.05 sec)
mysql> savepoint save2;                 -- 创建一个保存点save2

Query OK, 0 rows affected (0.01 sec)
mysql> insert into account values (2, '李四', 10000);  -- 在插入一条记录

Query OK, 1 row affected (0.00 sec)
mysql> select * from account;             -- 两条记录都在了

+----+--------+----------+
| id | name   | blance   |
+----+--------+----------+
|  1 | 张三   |   100.00 |
|  2 | 李四   | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> rollback to save2;                 -- 回滚到保存点save2(定向回滚)

Query OK, 0 rows affected (0.03 sec)
mysql> select * from account;             -- 一条记录没有了

+----+--------+--------+
| id | name   | blance |
+----+--------+--------+
|  1 | 张三   | 100.00 |
+----+--------+--------+
1 row in set (0.00 sec)
mysql> rollback; -- 直接rollback,回滚在最开始,哪怕你一个回滚点都没设置也可以

Query OK, 0 rows affected (0.00 sec)
mysql> select * from account;             -- 所有刚刚的记录没有了

Empty set (0.00 sec)

commit;--就是把该事务给提交了 无法回滚

但是一般我们很少手动rollback,事务大多数都是为了非正常情况

7.2 非正常演示1 - 证明未commit,客户端崩溃,MySQL自动会回滚(隔离级别设置为读未提交)

sql 复制代码
-- 终端A
mysql> select * from account;          -- 当前表内无数据
Empty set (0.00 sec)

mysql> show variables like 'autocommit'; -- 依旧自动提交
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit   | ON   |
+---------------+-------+
1 row in set (0.00 sec)

mysql> begin;                            --开启事务
Query OK, 0 rows affected (0.00 sec)

mysql> insert into account values (1, '张三', 100);   -- 插入记录
Query OK, 1 row affected (0.00 sec)

mysql> select * from account;           --数据已经存在,但没有commit,此时同时查看终端B
+----+--------+--------+
| id | name   | blance |
+----+--------+--------+
|  1 | 张三   | 100.00 |
+----+--------+--------+
1 row in set (0.00 sec)
mysql> Aborted                          -- ctrl + \ 异常终止MySQL 

--终端B
mysql> select * from account;           --终端A崩溃前
+----+--------+--------+
| id | name   | blance |
+----+--------+--------+
|  1 | 张三   | 100.00 |
+----+--------+--------+
1 row in set (0.00 sec)

mysql> select * from account;          --数据自动回滚
Empty set (0.00 sec)

7.3 非正常演示2 - 证明commit了,客户端崩溃,MySQL数据不会在受影响,已经持久化

sql 复制代码
--终端 A 
mysql> show variables like 'autocommit'; -- 依旧自动提交 
+---------------+-------+ 
| Variable_name | Value | 
+---------------+-------+ 
| autocommit | ON | 
+---------------+-------+ 
1 row in set (0.00 sec) 
 
mysql> select * from account; -- 当前表内无数据 
Empty set (0.00 sec) 
 
mysql> begin; -- 开启事务 
Query OK, 0 rows affected (0.00 sec) 
 
mysql> insert into account values (1, '张三', 100); -- 插入记录 
Query OK, 1 row affected (0.00 sec) 
 
mysql> commit; --提交事务 
Query OK, 0 rows affected (0.04 sec) 
 
mysql> Aborted -- ctrl + \ 异常终止MySQL 
 
--终端 B 
mysql> select * from account; --数据存在了,所以commit的作用是将数据持久化到MySQL中 
+----+--------+--------+ 
| id | name | blance | 
+----+--------+--------+ 
| 1 | 张三 | 100.00 | 
+----+--------+--------+ 
1 row in set (0.00 sec) 

7.4 非正常演示3 - 对比试验。证明begin操作会自动更改提交方式,不会受MySQL是否自动提交影响

手动begin就必须手动commit,跟是否是自动提交毫无关系

sql 复制代码
-- 终端 A 
mysql> select *from account; --查看历史数据 
+----+--------+--------+ 
| id | name | blance | 
+----+--------+--------+ 
| 1 | 张三 | 100.00 | 
+----+--------+--------+ 
1 row in set (0.00 sec) 
 
mysql> show variables like 'autocommit'; --查看事务提交方式 
+---------------+-------+ 
| Variable_name | Value | 
+---------------+-------+ 
| autocommit | ON | 
+---------------+-------+ 
1 row in set (0.00 sec) 
 
mysql> set autocommit=0; --关闭自动提交 
Query OK, 0 rows affected (0.00 sec) 
 
mysql> show variables like 'autocommit'; --查看关闭之后结果 
+---------------+-------+ 
| Variable_name | Value | 
+---------------+-------+ 
| autocommit | OFF | 
+---------------+-------+ 
1 row in set (0.00 sec) 
 
mysql> begin; --开启事务 
Query OK, 0 rows affected (0.00 sec) 
 
mysql> insert into account values (2, '李四', 10000); --插入记录 
Query OK, 1 row affected (0.00 sec) 
 
mysql> select *from account; --查看插入记录,同时查看终端B
+----+--------+----------+ 
| id | name | blance | 
+----+--------+----------+ 
| 1 | 张三 | 100.00 | 
| 2 | 李四 | 10000.00 | 
+----+--------+----------+ 
2 rows in set (0.00 sec) 
 
mysql> Aborted --再次异常终止 
 
-- 终端B 
mysql> select * from account; --终端A崩溃前 
+----+--------+----------+ 
| id | name | blance | 
+----+--------+----------+ 
| 1 | 张三 | 100.00 | 
| 2 | 李四 | 10000.00 | 
+----+--------+----------+ 
2 rows in set (0.00 sec) 
 
mysql> select * from account; --终端A崩溃后,自动回滚 
+----+--------+--------+ 
| id | name | blance | 
+----+--------+--------+ 
| 1 | 张三 | 100.00 | 
+----+--------+--------+ 
1 row in set (0.00 sec)

7.5 非正常演示4 - 证明单条 SQL 与事务的关系

sql 复制代码
--实验一 
-- 终端A 
mysql> select * from account; 
+----+--------+--------+ 
| id | name | blance | 
+----+--------+--------+ 
| 1 | 张三 | 100.00 | 
+----+--------+--------+ 
1 row in set (0.00 sec) 
 
mysql> show variables like 'autocommit'; 
+---------------+-------+ 
| Variable_name | Value | 
+---------------+-------+ 
| autocommit | ON | 
+---------------+-------+ 
1 row in set (0.00 sec) 
 
mysql> set autocommit=0; --关闭自动提交 
Query OK, 0 rows affected (0.00 sec) 
 
mysql> insert into account values (2, '李四', 10000); --插入记录
Query OK, 1 row affected (0.00 sec) 
 
mysql> select *from account; --查看结果,已经插入。此时可以在查看终端B 
+----+--------+----------+ 
| id | name | blance | 
+----+--------+----------+ 
| 1 | 张三 | 100.00 | 
| 2 | 李四 | 10000.00 | 
+----+--------+----------+ 
2 rows in set (0.00 sec) 
 
mysql> ^DBye --ctrl + \ or ctrl + d,终止终端 
 
--终端B 
mysql> select * from account; --终端A崩溃前 
+----+--------+----------+ 
| id | name | blance | 
+----+--------+----------+ 
| 1 | 张三 | 100.00 | 
| 2 | 李四 | 10000.00 | 
+----+--------+----------+ 
2 rows in set (0.00 sec) 
 
mysql> select * from account; --终端A崩溃后 
+----+--------+--------+ 
| id | name | blance | 
+----+--------+--------+ 
| 1 | 张三 | 100.00 | 
+----+--------+--------+ 
1 row in set (0.00 sec) 
 
 
-- 实验二 
--终端A 
mysql> show variables like 'autocommit'; --开启默认提交 
+---------------+-------+ 
| Variable_name | Value | 
+---------------+-------+ 
| autocommit | ON | 
+---------------+-------+ 
1 row in set (0.00 sec) 
 
mysql> select * from account; 
+----+--------+--------+ 
| id | name | blance | 
+----+--------+--------+ 
| 1 | 张三 | 100.00 | 
+----+--------+--------+ 
1 row in set (0.00 sec) 
 
mysql> insert into account values (2, '李四', 10000); 
Query OK, 1 row affected (0.01 sec) 
mysql> select *from account; --数据已经插入 
+----+--------+----------+ 
| id | name | blance | 
+----+--------+----------+ 
| 1 | 张三 | 100.00 | 
| 2 | 李四 | 10000.00 | 
+----+--------+----------+ 
2 rows in set (0.00 sec) 
 
mysql> Aborted --异常终止 
 
--终端B 
mysql> select * from account; --终端A崩溃前 
+----+--------+----------+ 
| id | name | blance | 
+----+--------+----------+ 
| 1 | 张三 | 100.00 | 
| 2 | 李四 | 10000.00 | 
+----+--------+----------+ 
2 rows in set (0.00 sec) 
 
mysql> select * from account; --终端A崩溃后,并不影响,已经持久化。autocommit起作用 
+----+--------+----------+ 
| id | name | blance | 
+----+--------+----------+ 
| 1 | 张三 | 100.00 | 
| 2 | 李四 | 10000.00 | 
+----+--------+----------+ 
2 rows in set (0.00 sec) 

7.6 结论

1、只要输入begin或者start transaction,事务便必须要通过commit提交,才会持久化,与是否设置set autocommit无关。

2、事务可以手动回滚,同时,当操作异常,MySQL会自动回滚

3、对于 InnoDB 每一条 SQL 语言都默认封装成事务,自动提交。(select有特殊情况,因为 MySQL 有MVCC )

从上面的例子,我们能看到事务本身的原子性(回滚),持久性(commit)

7.7 事务操作注意事项

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

2、如果一个事务被提交了(commit),则不可以回退(rollback)

3、可以选择回退到哪个保存点

savepoint save1; 设置保存点 rollback to save2;回退保存点

4、InnoDB 支持事务, MyISAM 不支持事务

5、开始事务可以使 start transaction 或者 begin

0

相关推荐
计算机学姐13 分钟前
基于SpringBoo的地方美食分享网站
java·vue.js·mysql·tomcat·mybatis·springboot·美食
GOTXX3 小时前
【Qt】Qt Creator开发基础:项目创建、界面解析与核心概念入门
开发语言·数据库·c++·qt·图形渲染·图形化界面·qt新手入门
猿小喵3 小时前
记录一次TDSQL网关夯住故障
运维·数据库·mysql
电商api接口开发3 小时前
如何在C#中使用LINQ对数据库进行查询操作?
数据库·c#·linq
hnsqls3 小时前
Redis 常问知识
数据库·redis·缓存
经年小栈5 小时前
性能优化-Spring参数配置、数据库连接参数配置、JVM调优
数据库·spring·性能优化
一个小白15 小时前
C++ 用红黑树封装map/set
java·数据库·c++
神奇小永哥5 小时前
redis之缓存雪崩
数据库·redis·缓存
麻花20136 小时前
sql server分析表大小
数据库
MySQL实战6 小时前
基于源码分析 HikariCP 常见参数的具体含义
mysql·连接池