当前位置: 首页 > news >正文

11. MySQL事务管理(上)

1. CURD不加控制,会有什么问题?

火车票售票系统tickets表
id
name
nums
10
西安<->兰州
1
客户端A 客户端B
if (nums > 0) { if (nums > 0) { 卖票 卖票 // update nums=nums - 1 update nums=nums - 1 } }
当客户端A检查还有一张票时,将票卖掉,还没有执行更新数据库时,客户端B检查了票数,发现大于0,于是又卖了一次票。然后A将票数更新回数据库。这是就出现了同一张票被卖了两次。

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

  1. 买票的过程得是原子的吧
  2. 买票互相应该不能影响吧
  3. 买完票应该要永久有效吧
  4. 买前,和买后都要是确定的状态吧

3. 什么是事务?-> 多条 mysql 语句对象.

事务就是一组DML语句组成,这些语句在逻辑上存在相关性,这一组DML语句要么全部成功,要么全部失败,是一个整体。MySQL提供一种机制,保证我们达到这样的效果。事务还规定不同的客户端看到的数据是不相同的。 要站在 mysql 的上层来看, 才有事务的这个概念, 这个有意义的行为往往对应多个 mysql 语句对应. 事务就是要做的或所做的事情,主要用于处理操作量大,复杂度高的数据。假设一种场景:你毕业了,学校的教务系统后台MySQL中,不在需要你的数据,要删除你的所有信息(一般不会:) ),那么要删除你的基本信息(姓名,电话,籍贯等)的同时,也删除和你有关的其他信息,比如:你的各科成绩,你在校表现,甚至你在论坛发过的文章等。这样, 就需要多条MySQL语句构成,那么所有这些操作合起来,就构成了一个事务。 正如我们上面所说,一个MySQL数据库,可不止你一个事务在运行,同一时刻,甚至有大量的请求被包装成事务,在向MySQL服务器发起事务处理请求。而每条事务至少一条SQL,最多很多SQL,这样如果大家都访问同样的表数据,在不加保护的情况,就绝对会出现问题。甚至,因为事务由多条SQL构成,那么,也会存在执行到一半出错或者不想再执行的情况,那么已经执行的怎么办呢?所有,一个完整的事务,绝对不是简单的sql集合,还需要满足如下四个属性:
  1. 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
  2. 一致性:在事务开始之前和事务结束以后,数据库的完整性(可预期性, 确定的结果)没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。(在技术层面上没有什么做额外工作, 这个 mysql 通过其他三个性质来保证一致性, 原子性 + 隔离性 + 持久性 -> 一致性)
  3. 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted )、读提交(read committed )、可重复读(repeatable read )和串行化 (Serializable )
  4. 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
上面四个属性,可以简称为 ACID 。原子性(Atomicity,或称不可分割性)一致性(Consistency)隔离性(Isolation,又称独立性)持久性(Durability)。

4. 为什么会出现事务

事务被MySQL编写者设计出来,本质是为了当应用程序访问数据库的时候, 事务能够简化我们的编程模型,不需要我们去考虑各种各样的潜在错误和并发问题。可以想一下当我们使用事务时,要么提交,要么回滚,我们不会去考虑网络异常了,服务器宕机了,同时更改一个数据怎么办对吧? 因此事务本质上是为了应用层服务的,而不是伴随着数据库系统天生就有的。备注:我们后面把MySQL中的一行信息,称为 一行记录

5. 事务的版本支持

在MySQL中只有使用了Innodb数据库引擎的数据库或表才支持事务,MyISAM不支持。查看数据库引擎
mysql> show engines; -- 表格显示 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 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 NULL Savepoints: NULL 9 rows in set (0.00 sec)

6. 事务提交方式

事务的提交方式常见的有两种:
  • 自动提交
  • 手动提交
查看事务提交方式
mysql> show variables like 'autocommit'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | ON | +---------------+-------+ 1 row in set (0.41 sec)
用SET来改变MySQL的自动提交模式:
mysql> SET AUTOCOMMIT=0 #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> SET AUTOCOMMIT=1 #SET AUTOCOMMIT=1 开启自动提交 Query OK, 0 rows affected (0.00 sec) mysql> show variables like 'autocommit'; +---------------+-------+ | Variable_name | Value | +---------------+-------+ | autocommit | ON | +---------------+-------+ 1 row in set (0.01 sec)

7. 事务常见操作方式(原子性 and 持久性)

简单银行用户表 提前准备
## Centos 7云服务器,默认开启3306 mysqld服务 [whb@VM-0-3-centos ~]$ sudo netstat -nltp [sudo] password for whb: Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State Foreign Address State PID/Program name tcp6 0 0 :::3306 :::* LISTEN 30415/mysqld ## 使用win cmd远程访问Centos 7云服务器,mysqld服务(需要win上也安装了MySQL,这里看到结果即可) ## 注意,使用本地mysql客户端,可能看不到链接效果,本地可能使用域间套接字,查不到链接 C:\Users\whb>mysql -uroot -p -h42.192.83.143 Enter password: *********** Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 3484 Server version: 5.7.33 MySQL Community Server (GPL) Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. ## 使用netstat查看链接情况,可知:mysql本质是一个客户端进程 [whb@VM-0-3-centos ~]$ sudo netstat -ntp Active Internet connections (w/o servers) Proto Recv-Q Send-Q Local Address Foreign Address State Foreign Address State PID/Program name tcp6 0 0 172.17.0.3:3306 113.132.141.236:19354 ESTABLISHED 30415/mysqld
为了便于演示,我们将mysql的默认隔离级别设置成 读未提交。具体操作我们后面专门会讲,现在已使用为主。
mysql> set global transaction isolation level READ UNCOMMITTED; Query OK, 0 rows affected (0.00 sec) mysql> quit Bye ##需要重启终端,进行查看 mysql> select @@tx_isolation; +-----------------+ | @@tx_isolation | +-----------------+ | READ-UNCOMMITTED | +-----------------+ 1 row in set, 1 warning (0.00 sec)
创建测试表
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> 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,回滚在最开始 或者 commit 提交(提交了不能再回滚了) Query OK, 0 rows affected (0.00 sec) mysql> select * from account; -- 所有刚刚的记录没有了 Empty set (0.00 sec)
非正常演示1 - 证明 未commit,客户端崩溃,MySQL自动会回滚(隔离级别设置为 读未提交
-- 终端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 异常终止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)
非正常演示2 - 证明 commit了,客户端崩溃,MySQL数据不会在受影响,已经持久化
--终端 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 --数据存在了,所以commit的作用是将数据持久 mysql> select * from account; 化到MySQL中 +----+------+----------+ | id | name | blance | +----+------+----------+ | 1 | 张三 | 100.00 | +----+------+----------+ 1 row in set (0.00 sec)
非正常演示3 - 对比试验。证明begin操作会自动更改提交方式,不会受MySQL是否自动提交影响
-- 终端 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)
非正常演示4 - 证明单条 SQL 与事务的关系: 单 sql 就是事务(默认会被 mysql 自动 commit)
--实验一 -- 终端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.1. 结论:
只要输入 begin或者 start transaction,事务便必须要通过commit提交,才会持久化,与是否设置 set autocommit无关。事务可以手动回滚,同时,当操作异常,MySQL会自动回滚
对于InnoDB每一条SQL语言都默认封装成事务, 自动提交。(select有特殊情况,因为MySQL有MVCC )
从上面的例子,我们能看到事务本身的 原子性(回滚)持久性(commit)
那么隔离性?一致性?
7.2. 事务操作注意事项
  1. 如果没有设置保存点,也可以回滚,只能回滚到事务的开始。直接使用rollback(前提是事务还没有提交)
  2. 如果一个事务被提交了(commit),则不可以回退(rollback)
  3. 可以选择回退到哪个保存点
  4. InnoDB支持事务,MyISAM不支持事务
  5. 开始事务可以使start transaction或者begin

8. 事务隔离级别

如何理解隔离性1 MySQL服务可能会同时被多个客户端进程(线程)访问,访问的方式以事务方式进行
一个事务可能由多条SQL构成,也就意味着,任何一个事务,都有 执行前,执行中,执行后的阶段。而 所谓的原子性,其实就是让用户层,要么看到执行前,要么看到执行后。执行中出现问题,可以随时回滚。所以单个事务,对用户表现出来的特性,就是原子性。
但,毕竟所有事务都要有个执行过程,那么在多个事务各自执行多个SQL的时候,就还是有可能会出现互相影响的情况。比如:多个事务同时访问同一张表,甚至同一行数据。
就如同你妈妈给你说:你要么别学,要学就学到最好。至于你怎么学,中间有什么困难,你妈妈不关心。那么你的学习,对你妈妈来讲,就是原子的。那么你学习过程中,很容易受别人干扰,此时,就需要将你的学习隔离开,保证你的学习环境是健康的。
数据库中,为了保证事务执行 过程中尽量不受干扰,就有了一个重要特征: 隔离性
数据库中,允许事务受 不同程度的干扰,就有了一种重要特征: 隔离级别
  1. 在事务的场景中, 隔离是必要的.
  1. 事务是整体的, 原子的
  2. 运行中的事务, 进行相互隔离
  1. 在事务运行中, 不会出现相互干扰, -> 隔离性
  2. 根据影响程度的不同, 隔离级别
8.1. 隔离级别
读未提交【Read Uncommitted】:在该隔离级别,所有的事务都可以看到其他事务没有提交的执行结果。(实际生产中不可能使用这种隔离级别的),但是相当于没有任何隔离性,也会有很多并发问题,如脏读,幻读,不可重复读等,我们上面为了做实验方便,用的就是这个隔离性。
读提交【Read Committed】 :该隔离级别是大多数数据库的默认的隔离级别(不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看到其他的已经提交的事务所做的改变。这种隔离级别会引起不可重复读,即一个事务执行时,如果多次select,可能得到不同的结果。
可重复读【Repeatable Read】:这是MySQL默认的隔离级别,它确保同一个事务,在执行中,多次读取操作数据时,会看到同样的数据行。但是会有 幻读问题。
串行化【Serializable】:这是事务的最高隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决了幻读的问题。它在每个读的数据行上面加上共享锁。但是可能会导致超时和锁竞争 (这种隔离级别太极端,实际生产基本不使用)
注: 在数据库当中, 如果是多个事务同时对一个资源写操作, 那么必须通过加锁串行化执行!
隔离级别如何实现:隔离,基本都是通过锁实现的,不同的隔离级别,锁的使用是不同的。常见有,表锁,行锁,读锁,写锁,间隙锁(GAP),Next-Key锁(GAP + 行锁)等。不过,我们目前现有这个认识就行,先关注上层使用。
8.2. 查看与设置隔离性
-- 查看
mysql> SELECT @@global.tx_isolation --查看全局隔级别 +-----------------+ | @@global.tx_isolation | +-----------------+ | REPEATABLE-READ | +-----------------+ 1 row in set, 1 warning (0.00 sec) mysql> SELECT @@session.tx_isolation ; --查看会话(当前)全局隔级别(默认把全局隔离级别拷贝到当前会话的隔离级别) +-----------------+ | @@session.tx_isolation | +-----------------+ | REPEATABLE-READ | +-----------------+ 1 row in set, 1 warning (0.00 sec) mysql> SELECT @@tx_isolation; --默认同上 +-----------------+ | @@tx_isolation | +-----------------+ | REPEATABLE-READ | +-----------------+ 1 row in set, 1 warning (0.00 sec)
--设置-- 设置当前会话 or 全局隔离级别语法
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}
--设置当前会话隔离性,另起一个会话,看不多,只影响当前会话
mysql> set session transaction isolation level serializable; -- 串行化 Query OK, 0 rows affected (0.00 sec) mysql> SELECT @@global.tx_isolation --全局隔离性还是RR +-----------------+ | @@global.tx_isolation | +-----------------+ | REPEATABLE-READ | +-----------------+ 1 row in set, 1 warning (0.00 sec) mysql> SELECT @@session.tx_isolation --会话隔离性成为串行化 +-----------------+ | @@session.tx_isolation | +-----------------+ | SERIALIZABLE | +-----------------+ 1 row in set, 1 warning (0.00 sec) mysql> SELECT @@tx_isolation; --同上 +-----------------+ | @@tx_isolation | +-----------------+ | SERIALIZABLE | +-----------------+ 1 row in set, 1 warning (0.00 sec)
--设置全局隔离性,另起一个会话,会被影响
mysql> set global transaction isolation level READ UNCOMMITTED; Query OK, 0 rows affected (0.00 sec) mysql> SELECT @@global.tx_isolation; +-----------------+ | @@global.tx_isolation | +-----------------+ | READ-UNCOMMITTED | +-----------------+ 1 row in set, 1 warning (0.00 sec) mysql> SELECT @@session.tx_isolation; +-----------------+ | @@session.tx_isolation | +-----------------+ | READ-UNCOMMITTED | +-----------------+ 1 row in set, 1 warning (0.00 sec) mysql> SELECT @@tx_isolation; +-----------------+ | @@tx_isolation | +-----------------+ | READ-UNCOMMITTED | +-----------------+ 1 row in set, 1 warning (0.00 sec)
-- 注意,如果没有现象,关闭mysql客户端,重新连接。
8.3. 读未提交【Read Uncommitted】
--几乎没有加锁,虽然 效率高,但是 问题太多,严重不建议采用--终端A-- 设置隔离级别为读未提交
mysql> set global transaction isolation level read uncommitted; Query OK, 0 rows affected (0.00 sec) --重启客户端 mysql> select @@tx_isolation; +-----------------+ | @@tx_isolation | +-----------------+ | READ-UNCOMMITTED | +-----------------+ 1 row in set, 1 warning (0.00 sec) mysql> select * from account; | id | name | blance | |----|----|----| | 1 | 张三 | 100.00 | | 2 | 李四 | 10000.00 | 2 rows in set (0.00 sec) mysql> begin --开启事务 Query OK, 0 rows affected (0.00 sec) mysql> update account set blance=123.0 where id=1; --更新指定行 Query OK, 1 row affected (0.05 sec) Rows matched: 1 Changed: 1 Warnings: 0 --没有commit哦!!!
--终端B
mysql> begin; mysql> select * from account; | id | name | blance | |----|----|----| | 1 | 张三 | 123.00 -- 读到终端A更新但是未commit的数据[insert,delete同样] | | 2 | 李四 | 10000.00 | 2 rows in set (0.00 sec)
--一个事务在执行中,读到另一个执行中事务的更新(或其他操作)但是未 commit的数据,这种现象叫做 脏读(dirty read)
8.4. 读提交【Read Committed】
-- 终端A
mysql> set global transaction isolation level read committed; Query OK, 0 rows affected (0.00 sec) --重启客户端 mysql> select * from account; --查看当前数据 | id | name | blance | |----|----|----| | 1 | 张三 | 123.00 | | 2 | 李四 | 10000.00 | 2 rows in set (0.00 sec) mysql> begin --手动开启事务,同步的开始终端B事务 Query OK, 0 rows affected (0.00 sec) mysql> update account set blance=321.0 where id=1 ; --更新张三数据 Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 --切换终端到终端B,查看数据。 mysql> commit; --commit提交! Query OK, 0 rows affected (0.01 sec) --切换终端到终端B,再次查看数据。
--终端B
mysql> begin --手动开启事务,和终端A一前一后 Query OK, 0 rows affected (0.00 sec) mysql> select * from account; --终端A commit之前,查看不到 | id | name | blance | |----|----|----| | 1 | 张三 | 123.00 --老的值 | | 2 | 李四 | 10000.00 | 2 rows in set (0.00 sec) --终端A commit之后,看到了! mysql> select *from account; | id | name | blance | |----|----|----| | 1 | 张三 | 321.00 --新的值 | | 2 | 李四 | 10000.00 | 2 rows in set (0.00 sec)
--but,此时还在当前事务中,并未commit,那么就造成了, 同一个事务内,同样的读取,在不同的时间段(依旧还在事务操作中!),读取到了不同的值,这种现象叫做 不可重复读(non reapeatable read)!!(这个是问题吗?? 是问题)
8.5. 可重复读【Repeatable Read】(MySQL 的默认隔离性)
--终端A
mysql> set global transaction isolation level repeatable read; --设置全局隔离级别RR Query OK, 0 rows affected (0.01 sec) --关闭终端重启 mysql> select @@tx_isolation; +-----------------+ | @@tx_isolation | +-----------------+ | REPEATABLE-READ | --隔离级别RR +-----------------+ 1 row in set, 1 warning (0.00 sec) mysql> select *from account; --查看当前数据 | id | name | blance | |----|----|----| | 1 | 张三 | 321.00 | | 2 | 李四 | 10000.00 | 2 rows in set (0.00 sec) mysql> begin --开启事务,同步的,终端B也开始事务 Query OK, 0 rows affected (0.00 sec) mysql> update account set blance=4321.0 where id=1; --更新数据 Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0 --切换到终端B,查看另一个事务是否能看到 mysql> commit; --提交事务 --切换终端到终端B,查看数据。
--终端B
mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from account; --终端A中事务 commit之前,查看当前表中数据,数据未更新 | id | name | blance | |----|----|----| | 1 | 张三 | 321.00 | | 2 | 李四 | 10000.00 | 2 rows in set (0.00 sec) mysql> select * from account; --终端A中事务 commit 之后,查看当前表中数据,数据未更新 | id | name | blance | |----|----|----| | 1 | 张三 | 321.00 | | 2 | 李四 | 10000.00 | 2 rows in set (0.00 sec) --可以看到,在终端B中,事务无论什么时候进行查找,看到的结果都是一致的,这叫做可重复读! mysql> commit; --结束事务 Query OK, 0 rows affected (0.00 sec) mysql> select * from account; --再次查看,看到最新的更新数据 | id | name | blance | |----|----|----| | 1 | 张三 | 4321.00 | | 2 | 李四 | 100
| 2 | 李四 | 10000.00 | 2 rows in set (0.00 sec)
--如果将上面的终端A中的update操作,改成insert操作,会有什么问题??--终端A
mysql> select *from account; | id | name | blance | |----|----|----| | 1 | 张三 | 321.00 | | 2 | 李四 | 10000.00 | 2 rows in set (0.00 sec) mysql> begin --开启事务,终端B同步开启 Query OK, 0 rows affected (0.00 sec) mysql> insert into account (id,name,blance) values(3, '王五', 5432.0); Query OK, 1 row affected (0.00 sec) --切换到终端B,查看另一个事务是否能看到 mysql> commit; --提交事务 Query OK, 0 rows affected (0.00 sec) --切换终端到终端B,查看数据。
--终端B
mysql> begin ; --开启事务 Query OK, 0 rows affected (0.00 sec) mysql> select * from account; --终端A commit前 查看 | id | name | blance | |----|----|----| | 1 | 张三 | 4321.00 | | 2 | 李四 | 10000.00 | 2 rows in set (0.00 sec) mysql> select * from account; --终端A commit后 查看 | id | name | blance | |----|----|----| | 1 | 张三 | 4321.00 | | 2 | 李四 | 10000.00 | 2 rows in set (0.00 sec) mysql> select * from account; | id | name | blance | |----|----|----| | 1 | 张三 | 4321.00 | | 2 | 李四 | 10000.00 | 2 rows in set (0.00 sec) mysql> commit; --结束事务 Query OK, 0 rows affected (0.00 sec) mysql> select * from account; --看到更新 | id | name | blance | |----|----|----| | 1 | 张三 | 4321.00 | | 2 | 李四 | 10000.00 | | 3 | 王五 | 5432.00 | 3 rows in set (0.00 sec)
--多次查看,发现终端A在对应事务中insert的数据,在终端B的事务周期中,也没有什么影响,也符合可重复的特点。但是,一般的数据库在可重复读情况的时候,无法屏蔽其他事务insert的数据( 为什么?因为隔离性实现是对数据加锁完成的,而insert待插入的数据因为并不存在,那么一般加锁无法屏蔽这类问题),会造成虽然大部分内容是可重复读的,但是insert的数据在可重复读情况被读取出来,导致多次查找时,会多查找出来新的记录,就如同产生了 幻觉。这种现象,叫做 幻读(phantom read)。很明显, MySQL在RR级别的时候,是解决了幻读问题的(解决的方式是用Next-Key锁(GAP+行锁)解决的。这块比较难,有兴趣同学了解一下)。
8.6. 串行化【serializable】
--对所有操作全部加锁( 事务),进行 串行化,不会有问题,但是只要串行化, 效率很低,几乎完全不会被采用--终端A
mysql> set global transaction isolation level serializable; Query OK, 0 rows affected (0.00 sec) mysql> select @@tx_isolation; +-----------------+ | @@tx_isolation | +-----------------+ | SERIALIZABLE | +-----------------+ 1 row in set, 1 warning (0.00 sec) mysql> begin --开启事务,终端B同步开启 Query OK, 0 rows affected (0.00 sec) mysql> select * from account; --两个读取不会串行化,共享锁 | id | name | blance | |----|----|----| | 1 | 张三 | 4321.00 | | 2 | 李四 | 10000.00 | | 3 | 王五 | 5432.00 | 3 rows in set (0.00 sec) mysql> update account set blance=1.00 where id=1; --终端A中有更新或者其他操作,会阻塞。直到终端B事务提交。 Query OK, 1 row affected (18.19 sec) Rows matched: 1 Changed: 1 Warnings: 0
--终端B
mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from account; --两个读取不会串行化 | id | name | blance | |----|----|----| | 1 | 张三 | 4321.00 | | 2 | 李四 | 10000.00 | | 3 | 王五 | 5432.00 | 3 rows in set (0.00 sec) mysql> commit; --提交之后,终端A中的update才会提交。 Query OK, 0 rows affected (0.00 sec)
总结:其中隔离级别越严格,安全性越高,但数据库的并发性能也就越低,往往需要在两者之间找一个平衡点。不可重复读的重点是修改和删除:同样的条件, 你读取过的数据,再次读取出来发现值不一样了幻读的重点在于新增:同样的条件, 第1次和第2次读出来的记录数不一样说明:mysql默认的隔离级别是可重复读,一般情况下不要修改上面的例子可以看出,事务也有长短事务这样的概念。事务间互相影响,指的是事务在并行执行的时候,即都没有commit的时候,影响会比较大。
隔离级别
脏读
不可重复读
幻读
加锁读
读未提交 (read uncommited)
不加锁
读已提交(read commited)
×
不加锁
可重复读(repeatable read)
×
×
不加锁
可串行化 (serializable)
×
×
×
加锁
√:会发生该问题 X:不会发生该问题

9. 一致性(Consistency)

mysql 中的一致性由原子性保护: 事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态。当数据库只包含事务成功提交的结果时,数据库处于一致性状态。如果系统运行发生中断,某个事务尚未完成而被迫中断,而该未完成的事务对数据库所做的修改已被写入数据库,此时数据库就处于一种不正确(不一致)的状态。因此一致性是通过原子性来保证的。
一致性由用户维护: 其实一致性和用户的业务逻辑强相关,一般MySQL提供技术支持,但是一致性还是要用户业务逻辑做支撑,也就是,一致性,是由用户决定的。
而技术上,通过AID保证C(一致性)

推荐阅读

如何实现事务的隔离性 - 简书 Innodb中的事务隔离级别和锁的关系 - 美团技术团队 Mysql 间隙锁原理,以及Repeatable Read隔离级别下可以防止幻读原理(百度) - aspirant - 博客园
备注:基本上,了解了上面的知识,在MySQL事务使用上,肯定没有问题。不过,这块设计很优秀,也是面试中可能被问到的,一般学生,如果能说出上面的内容,就已经不错了。但是如果我们能更详细,更深入的谈论这个问题,那么对我们的面试与学习肯定是大大的有帮助。不过接下来的内容,会比较难一些,听的不明白,也没有太大问题。这块内容,也要结合同学们听课的情况,如果时间紧张,我们就不讲了。如果有时间,可以现场给学生演示一下,在RR级别的时候,多个事务的update,多个事务的insert,多个事务的delete,是否会有加锁现象。现象结果是,update,insert,delete之间是会有加锁现象的,但是select和这些操作是不冲突的。这就是通过读写锁(锁有行锁或者表锁)+MVCC完成隔离性。
http://www.xdnf.cn/news/877699.html

相关文章:

  • C++11新特性(3)
  • Java高级 | 【实验四】Springboot 获取前端数据与返回Json数据
  • DeepSeek进阶应用(三):生成单词卡片高效学英语(可用于开发web应用、app等)
  • MySQL 表的内连和外连
  • 浮动测量技术在现代电力电子系统中的关键作用
  • 每日八股文6.4补
  • Day12
  • GQA(Grouped Query Attention):分组注意力机制的原理与实践《二》
  • 如何选择合适的分库分表策略
  • 前端表单验证进阶:如何使用 jQuery.validator.addMethod() 编写自定义验证器(全是干货,建议收藏)
  • 用布局管理器grid实现计算机界面
  • Python爬虫爬取天猫商品数据,详细教程【Python经典实战项目】
  • VBA中类的解读及应用第二十四讲:把源数据赋给类利用类完成查找
  • 【AI News | 20250604】每日AI进展
  • Markdown基础(1.2w字)
  • OPC UA 知识概述
  • 行业年终工作总结汇报PPT模版分享
  • 并发编程的问题与管程
  • LangChain深度解析:LLM应用开发利器
  • Redis常见使用场景解析
  • 【C语言个数最大最多】2022-4-1
  • 网络攻防技术十二:社会工程学
  • Mysql选择合适的字段创建索引
  • Java Lombok @Data 注解用法详解
  • 量子通信:从科幻走向现实的未来通信技术
  • 四、Sqoop 导入表数据子集
  • 使用C++调用python库
  • 东西方艺术的对话:彰显中国传统艺术之美与价值
  • 主流Agent开发平台学习笔记:扣子罗盘coze loop 功能拆解
  • Vue插件