本文主要是介绍MySQL 事务的概念及ACID属性和使用详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
《MySQL事务的概念及ACID属性和使用详解》MySQL通过多线程实现存储工作,因此在并发访问场景中,事务确保了数据操作的一致性和可靠性,下面通过本文给大家介绍MySQL事务的概念及ACID属性和...
一、什么是事务
- 定义:由一条或者多条
sql
语句构成的sql
集合体,这个集合体合在一起共同要完成某种任务。mysql通过多线程实现存储工作,因此在并发访问场景中,事务确保了数据操作的一致性和可靠性。 事务还规定 不同的客户端看到的数据是不相同的- 事务就是要做的或所做的事情,主要用于 处理操作量大,复杂度高的数据
- 假设一种场景:你毕业了, 学校的教务系统后台 MySQL 中,不在需要你的数据,要删除你的所有信息(一般不会:) ), 那么要删除你的 基本信息(姓名,电话,籍贯等)的同时,也删除和你有关的其他信息,比如:你的各科成绩,你在校表 现,甚至你在论坛发过的文章等。这样,就需要多条 MySQL 语句构成,那么所有这些操作合起来,就构成了一个事务。
- 背景:在没有控制的情况下进行
CURD
操作(创建、更新、读取、删除)可能导致数据不一致的问题。例如,在火车票售票系统中,两个用户同时尝试购买最后一张票,可能造成同一张票被卖出两次的现象
CURD满足什么属性,能解决上述问题?
- 买票的过程得是原子的吧
- 买票互相应该不能影响吧
- 买完票应该要永久有效吧
- 买前,和买后都要是确定的状态吧
二、事务的属性及使用
2.1 事务的 ACID 属性
一个 MySQL 数据库,可不止你一个事务在运行,同一时刻,甚至有大量的请求被包装成 事务,在向 MySQL 服务器发起事务处理请求。而每条事务至少一条 SQL
,最多很多 SQL
;这样如果大 家都访问同样的表数据,在不加保护的情况,就绝对会出现问题。甚至,因为事务由多条 SQL 构成,那 么,也会存在执行到一半出错或者不想再执行的情况,那么已经执行的怎么办呢?
所有,一个完整的事务,绝对不是简单的 sql
集合,还需要满足如下四个属性:
- 原子性(Atomicity,或称不可分割性):事务的所有操作要么全部完成,要么完全不执行,任何一部分失败都会导致整个事务的回滚。
- 一致性(Consistency):事务前后,数据库应保持一致的状态,即事务不应破坏数据库的完整性约束。(通过原子性,隔离性,持久性 AND 用户的配合实现一致性)
- 这表示写入的资料必须完 全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工 作。
- 隔离性(Isolation,又称独立性):事务之间的执行是相互隔离的,一个事务的执行不会受到其他事务的影响。
- 允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务 并发执行时由于交叉执行而导致数据的不一致。
- 事务隔离分为不同级别,包括读未提交(
Read uncommitted
)、读提交(read committed
)、可重复读(repeatable read
)和串行化 (Serializable
)
- 持久性(Durability):一旦事务提交,其对数据库所做的改变将是 永久性 的,即使系统发生故障也不会丢失。
2.2 为什么存在事务
刚才说的是多个 sql
在 交叉执行 可能会出现 并发问题 ,进而导致数据不一致,进而导致数据完整性,这都知道。但是对事务的理解不能光站在程序员角度理解,一定要站在数据库使用者角度考虑。
- 事务被
MySQL
编写者设计出来,但是 事务 并不是天然就有的,而是在用一段时间发现要有这个 事务。本质http://www.chinasem.cn是为了当应用程序访问数据库的时候,事务 能够简化我们的编程模型,不需要我们去考虑各种各样的潜在错误和并发问题。 - 只需要把告诉我你要干什么,把你的
sql
给我,我帮你封装成事务,帮你去运行。可以想一下当我们使用事务时,要么提交,要么回滚,我们不会去考虑网络异常了,服务器宕机了,同时更改一个数据怎么办对吧?这些问题统统不考虑。 - 因此 事务 本质上是为了应用层服务的,是为了让上层的应用服务更好的使用数据库。而不是伴随着数据库系统天生就有的。
备注:我们后面把 MySQL
中的一行信息,称为 一行记录
总结
- 解决并发问题:事务的设计初衷是为了应对并发操作带来的数据不一致问题,确保数据的完整性和一致性。
- 服务应用层:事务的本质是为应用层服务的,它简化了开发者的编程模型,使开发者可以专注于业务逻辑,而不用担心底层的数据操作细节。
- 对来的一大批 SQL–打包成事务-- 先描述再组织 管理
2.3 事务的版本支持
- 支持引擎:在MySQL中,只有使用了
InnoDB
数据库引擎的数据库或表才 - 支持事务,
MyISAM
不支持。
查看引擎:
mysql> show engines \G; # ... *************************** 5. row *************************** Engine: MyISAM Support: YES Comment: MyISAM storage engine Transactions: NO XA: NO Savepoints: NO # ... *************************** 7. row *************************** Engine: InnoDB Support: DEFAULT Comment: Supports transactions, row-level locking, and foreign keys Transactions: YES XA: YES Savepoints: YES # ... 9 rows in set (0.01 sec)
2.4 事务的提交方式
- 自动提交:事务默认是自动提交的。
- 手动提交
查看:
show variables like 'autocommit';
+---------------gRvDhhLTSb+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+--php-----+
设置:
set autocommit=0; -- 设置为手动提交 set autocommit=1; -- 设置为自动提交
2.5 事务的常见操作方式
linux下 mysql
是一个命令行式的客户端进程。但不仅如此 mysql
客户端还有很多其他客户端如图形化界面版的,还有其他语言版的。
mysql
是一套网络服务进程也就意味着除了本地主机,远端主机也可以连接myql
换句话说 mysql
服务器可能会被多个客户端同时访问
root@VM-8-10-Ubuntu:/home/lighthouse# netstat -nltp; # 需要切换成 root 用户 Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN 13847/mysqld tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 823/sshd: /usr/sbin
为了更好做事务方面演示,我们将 mysql
的默认隔离级别设置成读未提交,隔离级别后面我们专门具体说。
设置全局事务隔离级别 读为提交
set global transaction isolation level READ UNCOMMITTED;
需要重启终端,进行查看,可以看到
mysql> quit Bye # 需要重启终端,进行查看 mysql> select @@tx_isolation; # 5.0 版本 mysql> select @@transaction_isolation; # 8.0 版本 +-------------------------+ | @@transaction_isolation | +-------------------------+ | READ-UNCOMMITTED | +-------------------------+ REPEATABLE-READ # 默认是这个
- 然后新起两个
mysql
客户端,为什么这么做呢,我们主要是为了研究事务,研究事务就要研究多个客户端并发访问的情况。 - 其次
mysql
是有隔离性和隔离级别的,所以目前把隔离级别跳到最低,一个mysql
做操作,另一个mysql
就能看到。 - 这样很能清楚看到事务交叉所带来的问题。
【案例】:
① 创建一个员工表
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;
两个 mysql客户端 可以并发访问这张表,准备工作全部就绪,下面我们来做试验!
mysql> show processlist; +------+-----------------+-----------+--------+---------+---------+------------------------+------------------+ | Id | User | Host | db | Command | Time | State | Info | +------+-----------------+-----------+--------+---------+---------+------------------------+------------------+ | 5 | event_scheduler | localhost | NULL | Daemon | 4481367 | China编程Waiting on empty queue | NULL | | 2385 | root | localhost | learn3 | Query | 0 | init | show processlist | | 2386 | root | localhost | learn3 | Sleep | 41 | javascript | NULL | +------+-----------------+-----------+--------+---------+---------+------------------------+------------------+
⭕正常演示 - 事务的开始与回滚
② 启动事务
start transaction; -- 方式一 begin; -- 方式二
③ 设置保存点
savepoint s1;
④ 回滚到保存点
rollback s1;
设置和回滚使用如下:
⑤ 提交事务
如果想结束这个事务,那就提交一下,相当于把这个事务提交了
commit;
⑥ 回滚事务
- 那我们以后是不是必须要设置保持点才能回滚呢?
- 并不是!没有保存点只是没有办法定向回滚了。但是可以直接回滚到最开始!
rollback;
直接回滚到最开始!然后数据就全没了。即使是结束事务,回归到单 sql
也是没有的。
非正常操作
下面都是默认开启 自动提交 的,而且 演示 1 和 演示 2 表开始的时候均无数据(empty
)
① 非正常演示1: 证明未 commit
,客户端崩溃,MySQL自动会回滚(隔离级别设置为读未提交)
-- 终端A begin; -- 开启事务 insert into account values (1, '张三', 100); -- 插入记录 mysql> select * from account; -- 数据已经存在,但没有commit,此时同时查看终端B +----+--------+--------+ | id | name | blance | +----+--------+--------+ | 1 | 张三 | 100.00 | +----+--------+--------+
让 A 中止,然后再查看A终止前后 B 的数据,如下:
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; -- 终端 A 崩溃后 Empty set (0.00 sec)
上面我们发现 数据自动进行了回滚
② 非正常演示2 - 证明 commit
了,客户端崩溃,MySQL数据不会在受影响,已经持久化
-- 终端 A mysql> begin; -- 开启事务 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 -- 终端 A 终止后,切换终端 B 查看数据 mysql> select * from account; +----+--------+--------+ | id | name | blance | +----+--------+--------+ | 1 | 张三 | 100.00 | +----+--------+--------+ 1 row in set (0.00 sec)
相比于上面这里我们在异常终止终端 A 之前,已经把数据 commit
了,然后再查看 B 发现数据还在,没有回滚 – 持久化保存
- 数据存在了,所以
commit
的作用是将数据持久化到MySQL中
③ 非正常演示3 - 对比演示 1。证明 begin
操作会自动更改提交方式,不会受 MySQL
是否自动提交影响
-- 终端 A,历史是有数据的 mysql> select * from account; +----+--------+--------+ | id | name | blance | +----+--------+--------+ | 1 | 张三 | 100.00 | +----+--------+--------+ -- 事务 A 开始时: 是默认自动提交的查 mysql> show variables like 'autocommit'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | ON | +---------------+-------+ 1 row in set (0.00 sec) -- 关闭自动提交 mysql> set autocommit=0; mysql> show variables like 'autocommit'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | OFF | +---------------+-------+
然后再开始进行事务操作,如下:
mysql> begin; -- 开启事务 mysql> insert into account values (2, '李四', 10000); -- 插入记录 -- 在终端 A 中查看插入记录 mysql> select *from account; +----+--------+----------+ | id | name | blance | +----+--------+----------+ | 1 | 张三 | 100.00 | | 2 | 李四 | 10000.00 | +----+--------+----------+
然后再次让终端 A 异常终止,查看 A 终止前后 B 的数据,如下:
mysql> Aborted -- 再次异常终止 A -- 切换到终端 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 是类似的,可以知道 begin
与是否设置 set autocommit
无关,证明正确
④ 非正常演示4 - 证明单条 SQL 与事务的关系【autocommit】
实验一:
-- 终端 A,开始时是有数据的 mysql> select * from account; +----+--------+--------+ | id | name | blance | +----+--------+--------+ | 1 | 张三 | 100.00 | +----+--------+--------+ mysql> set autocommit=0; -- 关闭自动提交 mysql> insert into account values (2, '李四', 10000); -- 插入记录 Query OK, 1 row affected (0.00 sec) mysql> select *from account; -- 在终端 A 中查看结果,已经插入。此时可以在查看终端B +----+--------+----------+ | id | name | blance | +----+--------+----------+ | 1 | 张三 | 100.00 | | 2 | 李四 | 10000.00 | +----+--------+----------+ 2 rows in set (0.00 sec) mysql> ^D Bye -- 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 | +----+--------+--------+
上面数据发生了回滚
实验二:
-- 终端A mysql> show variables like 'autocommit'; -- 开启默认提交 +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | ON | +---------------+-------+ 1 row in set (0.00 sec) mysql> insert into account values (2, '李四', 10000); 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 | +----+--------+----------+ mysql> select * from account; -- 终端A崩溃后,并不影响,已经持久化。autocommit起作用 +----+--------+----------+ | id | name | blance | +----+--------+----------+ | 1 | 张三 | 100.00 | | 2 | 李四 | 10000.00 | +----+--------+----------+
结论:
- 未提交时客户端崩溃:事务可以手动回滚,同时操作异常时,
MySQL
会自动回滚未提交的事务。 - 已提交时客户端崩溃:只要输入
begin
或者start transaction
,事务便必须要通过commit
提交,才会持久化,与是否设置set autocommit
无关 - 单条SQL与事务:默认情况下,对于
InnoDB
每一条 SQL 语言都默认封装成事务,自动提交。如果关闭自动提交,单条SQL语句也需要手动提交或回滚【select
有特殊情况,因为 MySQL 有 MVCC】
从上面的例子,我们能看到事务本身的原子性(回滚),持久性(commit)
注意事项
- 回滚:如果没有设置保存点,只能回滚到事务的开始,直接使用 rollback(前提是事务 还没有提交)
- 提交:事务提交后无法回滚。
- 保存点:可以选择回退到哪个保存点。
- 版本支持:
InnoDB
支持事务,MyISAM
不支持事务 - 启动事务:可以使用
start transaction
或begin
到此这篇关于MySQL 事务的概念及ACID属性和使用详解的文章就介绍到这了,更多相关mysql事务ACID属性内容请搜索China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持China编程(www.chinasem.cn)!
这篇关于MySQL 事务的概念及ACID属性和使用详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!