《数据库》MySQL事务
1. 什么是事务
1.1 概念
一个最小的不可再分的工作单元,一个事务对应一个完整的业务,一个完整的业务需要批量的DML(insert、update、delete)语句共同联合完成,事务只针对DML语句
数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位,事务由事务开始与事务结束之间执行的全部数据库操作组成。
- mysql中,只有Innodb是支持事务
mysql> show engines; # 查看Mysql的所有执行引擎可以到,默认的执行引擎是innoDB 支持事务,行级锁定和外键。
1.2 示例
以银行转账为例,账户转账是一个完整的业务,最小的单元,不可再分——也就是说银行账户转账是一个事务
update account set money = money - 200 where id = 1;
update account set money = money + 200 where id = 2;
分析
- 以上两个DML语句必须同时成功或者同时失败
最小单元不可再分,当第一条DML语句执行成功后,并不能将底层数据库中的第一个账户的数据修改,只是将操作记录了一下,这个记录是在内存中完成的
当第二条DML语句执行成功后,和底层数据库文件中的数据完成同步
若第二条DML语句执行失败,则清空所有的历史操作记录,要完成以上的功能必须借助事务
1.3 数据库事务处理原则
保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式
当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来
要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态。
为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态
2. 事务的特性
2.1 原子性
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生,保证事务的整体性
例:zhangsan给lisi转帐200元钱的时候只执行了扣款语句,就提交了,此时如果突然断电,zhangsan账号已经发生了扣款,lisi账号却没收到加款,在生活中就会引起纠纷。这种情况就需要事务的原子性来保证事务要么都执行,要么就都不执行
2.2 一致性
一致性是指事务必须使数据库从一个一致性状态变换到另外一个一致性状态
分析
当事务完成时,数据必须处于一致状态
在事务开始前,数据库中存储的数据处于一致状态
在正在进行的事务中,数据可能处于不一致的状态
当事务成功完成时,数据必须再次回到一致状态
例:对银行转帐事务,不管事务成功还是失败,应该保证事务结束后表中zhangsan和lisi的存款总额跟事务执行前一致,如:zhangsan的账户有1000元,lisi的账户有1000元,现在zhangsan转账200元给lisi,不管事务成功还是失败,转账前后zhangsan和lisi的存款总额都应该是2000元
2.3 隔离性
隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离,每个事务都有各自的完整数据空间
2.4 持久性
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
注意:ACID面试常考
3. MySQL使用事务
3.1 MySQL的事务操作
1.开启事务:start transaction
任何一条DML语句(insert、update、delete)执行,标志事务的开启
命令:begin 或 start transaction
2.提交事务:commit transaction
成功的结束,将所有的DML语句操作历史记录和底层硬盘数据来一次同步
命令:commit
3.回滚事务:rollback transaction
失败的结束,将所有的DML语句操作历史记录全部清空
命令:rollback
3.2 使用事务的方法
方式1 | 方法2 | 方法3 | 方法4 |
---|---|---|---|
begin | start transaction | set autocommit=1 | set autocommit=0 |
sql语句 | sql语句 | sql语句 | sql语句 |
commit | rollback | commit | rollback | commit | rollback |
3.3 分析
- 开始事务:begin 或 start transaction ,注意:mysql5.5 以上的版本,不需要手工书写begin,只要你执行的是一个DML,会自动在前面加一个begin命令,此方法又称为显示开启事务
- MySQL中用 autocommit参数设置事务是否自动提交
- commit : 手动提交事务,一旦事务提交成功 ,就说明具备ACID特性了
- rollback :回滚事务,将内存中,已执行过的操作,回滚回去
3.4 注意
事务是基于当前数据库连接而言的,而不是基于表,一个事务可以由操作不同表的多条SQL组成
分析:下面画出了两个数据库连接,假设连接
A
中开启了一个事务,那后续过来的所有SQL
都会被加入到一个事务中,也就是图中连接A
,后面的SQL②、SQL③、SQL④、SQL⑤
这四条都会被加入到一个事务中,只要在未曾收到commit/rollback
命令之前,这个连接来的所有SQL
都会加入到同一个事务中,因此对于这点要牢记,开启事务后一定要做提交或回滚处理。
3.5 自动提交策略
MySQL默认已经开启自动提交,可以通过对应的设置来开启或者关闭自动提交
查看自动提交设置
mysql> select @@autocommit; # @@用于查看全局变量mysql> show variables like 'autocommit'; # 或者
# 可以在C:\ProgramData\MySQL\MySQL Server 8.0\my.ini配置文件中增加一行
autocommit=0
3.6 示例1
测试begin事务的一致性
mysql> create database mydb17_transcation;mysql> use mydb17_transcation;mysql> create table account (id int(10) primary key not null, name varchar (40), money double);mysql> insert into account values(1, '张三', 1000);mysql> insert into account values(2, '李四', 2000);mysql> begin;mysql> update account set money=3000 where id=2;mysql> select * from account;
+----+--------+-------+
| id | name | money |
+----+--------+-------+
| 1 | 张三 | 1000 |
| 2 | 李四 | 3000 |
+----+--------+-------+mysql> exit; # 断开连接
Bye
PS C:\Users\Administrator> mysql -uroot -p123456mysql> use mydb17_transcation;mysql> show tables;mysql> select * from account;
+----+--------+-------+
| id | name | money |
+----+--------+-------+
| 1 | 张三 | 1000 |
| 2 | 李四 | 2000 | # 发现未提交事务,数据没变
# 手动提交事务
mysql> begin;mysql> update account set money=3000 where id=2;mysql> commit; # 提交事务mysql> exit;
Bye
PS C:\Users\Administrator> mysql -uroot -p123456mysql> use mydb17_transcation;mysql> select * from account;
+----+--------+-------+
| id | name | money |
+----+--------+-------+
| 1 | 张三 | 1000 |
| 2 | 李四 | 3000 | # 内容改变
# 回滚事务
mysql> select * from account;
+----+--------+-------+
| id | name | money |
+----+--------+-------+
| 1 | 张三 | 1000 |
| 2 | 李四 | 3000 |
+----+--------+-------+
2 rows in set (0.00 sec)mysql> begin;mysql> delete from account where id=1; # 删除记录mysql> select * from account;
+----+--------+-------+
| id | name | money |
+----+--------+-------+
| 2 | 李四 | 3000 |
+----+--------+-------+
1 row in set (0.01 sec)mysql> rollback; # 回滚事务mysql> select * from account;
+----+--------+-------+
| id | name | money |
+----+--------+-------+
| 1 | 张三 | 1000 |
| 2 | 李四 | 3000 |
+----+--------+-------+
3.7 示例2
关闭自动提交
mysql> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
| 1 |
+--------------+mysql> set autocommit=0;mysql> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
| 0 |
+--------------+mysql> insert into account values(3, '王五', 3000); # 省略beginmysql> commit; # 提交mysql> select * from account;
+----+--------+-------+
| id | name | money |
+----+--------+-------+
| 1 | 张三 | 1000 |
| 2 | 李四 | 3000 |
| 3 | 王五 | 3000 |
+----+--------+-------+
3.8 设置回滚点
- 作用:可以创建一个或多个回滚点,将事务回滚到标记点
- 格式
savepoint 回滚点名rollback to 回滚点# 注意:手动设置回滚点,需要关闭事务自动提交,若自动提交已开启,则语句执行后代表事务已提交,所有回滚点都会删除,此时执行手动回滚会报错,即:
ERROR 1305 (42000): SAVEPOINT ** does not exist
- 示例
mysql> exitPS C:\Users\Administrator> mysql -uroot -p123456mysql> use mydb17_transcation;mysql> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
| 1 |
+--------------+
1 row in set (0.00 sec)mysql> set autocommit=0; # 关闭自动提交mysql> select * from account;mysql> savepoint s1; # 创建1个回滚点mysql> update account set money=money+1000 where id=1; # 金额+1000mysql> select * from account;mysql> rollback to s1; # 回滚到s1mysql> select * from account;
3.9 事务的隐式提交
概念:因为某些特殊的语句而导致事务自动提交的情况称为隐式提交
那些事务不会自动提交
使用start transaction开启事务
使用begin开启事务
把系统变量autocommit的值设置为OFF | 0
常见隐式提交
- 示例
mysql> exitPS C:\Users\Administrator> mysql -uroot -p123456mysql> use mydb17_transcation;mysql> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
| 1 |
+--------------+mysql> insert into account values(4, '刘七', 4000);mysql> insert into account values(5, '孙鹏', 5000);select * from account;mysql> rollback; # 隐式提交后回滚mysql> select * from account; # 未变化
- 注意
在MySQL中,为了避免隐式提交造成的隐患,建议使用显式提交操作,即通过手动执行COMMIT或ROLLBACK来明确事务的完成或回滚
sql查询操作会在事务的上下文中执行,但是它并没有对数据进行写的操作,所以事务不会持有任何锁,在查询结束后会立即提交,因此,InnoDB所有的操作都是在事务中进行的
4. InnoDB事务的ACID如何保证
4.1 WAL技术
redo log 又叫“重做日志”,是存储引擎层 (innoDB) 生成的日志,记录的是"物理级别"上的页修改操作,比如页号x,偏移量y写入了'z'数据,主要目的为了保证数据不丢失,当MySQL发生宕机的时候,可以利用redo log日志进行数据恢复,用于确保事务的持久性
一条SQL更新语句怎么运行:
当有一条记录要更新时,InnoDB 引擎就会先把记录写到 redo log磁盘文件中,此时更新就算结束了
在系统空闲的时候,将上述操作记录更新到磁盘文件中
比如:小店做生意,有个粉板,有个账本,来客点餐了先写粉板,等不忙的时候再写账本
性能不够,缓存来凑,由于CPU的性能远远大于磁盘,为了消除这个鸿沟,引入了两个缓存
Buffer Pool :缓冲池,包含了磁盘中部分数据页的映射,作为访问数据库的缓冲
redo log buffer:redo log本质是磁盘上的日志文件,为题提交读写增加了缓冲区来存放重做日志
WAL:Write-Ahead Logging,预写日志系统,先写日志,再写磁盘,只有日志写入成功,才算事务提交成功的技术思想在MySQL叫做WAL技术,其基本过程如下:
先将原始数据从磁盘中读入到Buffer Pool中
修改Buffer Pool中的数据
生成一条重做日志并写入redo log buffer,记录数据修改后的值
当事务提交时,将redo log buffer中的内容追加磁盘中的redo log文件中
将磁盘日志文件redo log file 内容刷到数据库表中(也称为“脏刷”)
为什么为什么要"多此一举"先写入到redo log磁盘文件中,然后再脏刷到数据库表中?而不直接落到数据库表中?
数据在MySQL中存储是以页为单位,事务中的数据可能遍布在不同的页中,如果直接写入到对应的页中,是随机IO写入
磁盘顺序IO性能远高于随机IO
redo log是通过顺序IO"追加"的方式写入到文件末尾,只包含真正需要写入的,无效 IO 减少,但刷脏页以Page为单位,一个Page上的修改整页都要写且为随机IO
4.2 redo log相关参数
redo log容量查看
mysql> show variables like 'innodb_redo_log_capacity';
+--------------------------+-----------+
| Variable_name | Value |
+--------------------------+-----------+
| innodb_redo_log_capacity | 104857600 | # 默认100MB
+--------------------------+-----------+
- 存储位置:共生成32个 redo log 文件,每个文件的大小等于 1/32 * innodb_redo_log_capacity,redo log 有两种:正在使用的和未被使用的,分别使用 #ib_redoNN 和 #ib_redoNN_tmp其中NN是重做日志文件编号
C:\ProgramData\MySQL\MySQL Server 8.0\Data\#innodb_redo
- 查看状态
mysql> show global status like '%innodb%redo%';
+-------------------------------------+------------+
| Variable_name | Value |
+-------------------------------------+------------+
| Innodb_redo_log_read_only | OFF |# 当前redo不是处于RO状态
| Innodb_redo_log_uuid | 3173314963 |# 归档用
| Innodb_redo_log_checkpoint_lsn | 635392598 |# 最新的checkpoint
| Innodb_redo_log_current_lsn | 635392598 |# 当前LSN
| Innodb_redo_log_flushed_to_disk_lsn | 635392598 |# 已刷盘的LSN
| Innodb_redo_log_logical_size | 512 |# 当前活跃事务使用log大小
| Innodb_redo_log_physical_size | 6553600 |# 当前正在使用中的log大小
| Innodb_redo_log_capacity_resized | 104857600 |# 总大小
| Innodb_redo_log_resize_status | OK |# 状态
| Innodb_redo_log_enabled | ON |
+-------------------------------------+------------+
4.3 MySQL CSR--前滚
MySQL在启动时,必须保证redo日志文件和数据文件LSN必须一致, 如果不一致就会触发CSR,最终保证一致
LSN 称为日志的逻辑序列号(log sequence number)在InnoDB存储引擎中,LSN占8个字节,LSN的值会随着日志的写入而逐渐变大,内容包括:
数据页的版本信息。
写入的日志总量。通过LSN开始号码和结束号码可以计算出写入的日志量。
可知道检查点的位置。
前滚过程:
1.我们做了一个事务begin;update ....;commit;
2. begin时会立即分配一个TXID=tx_01.
3. update时,会将需要修改的数据页(dp_01,LSN=101),加载到data buffer中
4. DBWR线程,会进行dp_01数据页修改更新,并更新LSN=102
5. LOGBWR日志写线程,会将dp_01数据页的变化+LSN+TXID存储到redobuffer
6. 执行commit时,LGWR日志写线程会将redobuffer信息写入redolog日志文件中,基于WAL原则,在日志完全写入磁盘后,commit命令才执行成功,(会将此日志打上commit标记)
7. 假如此时宕机,内存脏页没有来得及写入磁盘,内存数据全部丢失
8. MySQL再次重启时,要求redolog和磁盘数据页的LSN是一致的,但是磁盘的值为:LSN=101,而redolog中LSN=102
9.MySQL此时无法正常启动,MySQL触发CSR.在内存追平LSN号,触发ckpt,将内存数据页更新到磁盘,从而保证磁盘数据页和redolog LSN一致,此时MySQL正常启动
10.以上的工作过程,我们把它称之为基于REDO的"前滚操作"
4.4 undo 回滚日志
undo(回滚)的作用是保证事务的原子性和隔离性,同时支持快照技术和数据恢复
作用
原子性(Atomicity):通过undo,可以在事务回滚时将数据恢复到修改之前的状态,确保事务要么完全执行,要么完全回滚,避免了部分操作的影响。
隔离性(Isolation):并发事务可能同时修改同一个数据,使用undo可以提供事务的私有数据版本,保证每个事务独立地看到修改之前的数据,从而实现隔离性。
快照技术:undo提供了事务修改之前的数据状态的快照,这意味着在事务执行过程中,可以回退到之前的状态。这对于实现多版本并发控制(MVCC)非常重要,每个事务可以读取数据的一致版本,而不会受到其他事务的修改影响。
数据恢复:undo日志记录了事务的修改操作和之前的数据状态,当系统发生故障或崩溃时,可以使用undo日志来恢复未提交的事务或未完成的操作。这种数据恢复机制对于数据库的持久性和可靠性非常重要。
5. 事务的隔离级别
5.1 概念
隔离(Isolate)顾名思义就是将事务与另一个事务隔离开,为什么要隔离呢?如果一个事务正在操作的数据被另一个事务修改或删除了,最后的执行结果可能无法达到预期,隔离级别是指多个并发事务之间的隔离程度
多个线程开启各自事务操作数据库中数据时,数据库系统要负责隔离操作,以保证各个线程在获取数据时的准确性,如果不考虑隔离性,可能会引发如下问题:
幻想读
不可重复读取
脏读
5.2 脏读
脏读(Dirty Read)指:一个事务读取了另外一个事务未提交的数据,在这种情况下,如果另一个事务回滚,则第一个事务读取到的数据实际上是无效的或错误的
示例如下图:
分析:上图中,
DB
连接①/事务A
正在执行下单业务,目前扣减库存、增加订单两条SQL
已经完成了,恰巧此时DB
连接②/事务B
跑过来读取了一下库存剩余数量,就将事务A
已经扣减之后的库存数量读回去了。但好巧不巧,事务A
在添加物流信息时,执行异常导致事务A
全部回滚,也就是原本扣的库存又会增加回去,这种问题就是脏读,其会导致致数据不一致,进而影响整个业务系统出现问题。
5.3 不可重复读
不可重复读(Non-repeatable Read):指在一个事务中,多次读取同一数据,先后读取到的数据不一致,示例如下:
分析:事务
A
执行下单业务时,因为添加物流信息的时候出错了,导致整个事务回滚,事务回滚完成后,事务A
就结束了。但事务B
却并未结束,在事务B
中,在事务A
执行时读取了一次剩余库存,然后在事务回滚后又读取了一次剩余库存,其中:B
事务第一次读到的剩余库存是扣减之后的,第二次读到的剩余库存则是扣减之前的(因为A
事务回滚又加回去了)。上述案例中,同一个事务中读取同一数据,结果却并不一致,也就说明了该数据存在不可重复读问题。
要理解不可重复读和可重复读的意思是,可重复读:在同一事务中,不管读取多少次,读到的数据都是相同的。
5.4 幻读
示例:
假设此时平台要升级,用户表中的性别字段,原本是以「男、女」的形式保存数据,现在平台升级后要求改为「
0、1
」代替。因此事务
A
开始更改表中所有数据的性别字段,当负责执行事务A
的线程正在更改最后一条表数据时,此时事务B
来了,正好向用户表中插入了一条「性别=男」的数据并提交了,然后事务A
改完原本的最后一条数据后,当再次去查询用户表时,结果会发现表中依旧还存在一条「性别=男」的数据,似乎跟产生了幻觉一样。
幻读问题的原因是在于:另外一个事务在第一个事务要处理的目标数据范围之内新增了数据,然后先于第一个事务提交造成的问题。
幻读和不可重复读的区别:
不可重复读:是同一条SQL查询的内容不同(被修改了)
幻读:查询的结果条数不同(增加了、或者删除了记录)
5.5 隔离级别
为了处理这些问题,SQL标准定义了以下几种事务隔离级别:
READ-UNCOMMITTED(读取未提交) : 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
READ-COMMITTED(读取已提交) : 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
REPEATABLE-READ(可重复读) : 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
SERIALIZABLE(可串行化) : 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
总结如下表:
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
Read uncommitted(读取未提交) | √ | √ | √ |
Read committed(读取已提交) | × | √ | √ |
Repeatable read(可重复读) | × | × | √ |
Serializable(可串行化) | × | x | x |
5.6 查看隔离级别
sql查询查看
mysql> select @@GLOBAL.transaction_isolation, @@transaction_isolation;
+--------------------------------+-------------------------+
| @@GLOBAL.transaction_isolation | @@transaction_isolation |
+--------------------------------+-------------------------+
| REPEATABLE-READ | REPEATABLE-READ |
+--------------------------------+-------------------------+
查看变量
mysql> show variables like "transaction_isolation";
+-----------------------+-----------------+
| Variable_name | Value |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
5.7 设置隔离级别
- 设置当前连接隔离
# 设置read uncommitted(读未提交)级别
mysql> set session transaction isolation level read uncommitted;#设置read committed(读已提交)级别:
mysql> set session transaction isolation level read committed;#设置repeatable read(可重复度)级别:
mysql> set session transaction isolation level repeatable read;# 设置serializable(串行化)级别:
mysql> set session transaction isolation level serializable;# 查看当前级别
mysql> show variables like "transaction_isolation";
- 设置全局隔离
mysql> set global transaction isolation level 隔离级别;
5.8 示例
准备工作:建立并发环境并建立2个mysql链接且登录数据库
- read uncommitted(读未提交)级别
# 连接1:
mysql> use mydb17_transcationmysql> create table ac (id int(2) primary key not null, name varchar (10), money double);mysql> insert into ac values(1, '张三', 1000);mysql> insert into ac values(2, '李四', 2000);mysql> select * from ac;mysql> show variables like "transaction_isolation"; # 查看当前隔离级别mysql> set autocommit = 0; # 设置取消自动提交
# 连接2:
mysql> use mydb17_transcationmysql> select * from ac;mysql> set autocommit = 0;
# 连接1:
mysql> set session transaction isolation level read uncommitted;mysql> select * from ac;
+----+--------+-------+
| id | name | money |
+----+--------+-------+
| 1 | 张三 | 1000 |
| 2 | 李四 | 2000 |
+----+--------+-------+
# 连接2:
mysql> set session transaction isolation level read uncommitted;mysql> begin; # 开始一个事务mysql> update ac set money=5000 where name="张三"; # 更新数据# 注意:此时连接2的事务未提交
# 连接1:
mysql> select * from ac;
+----+--------+-------+
| id | name | money |
+----+--------+-------+
| 1 | 张三 | 5000 |
| 2 | 李四 | 2000 |
+----+--------+-------+# 连接1中前后2次查询结果不同# read uncommitted(读未提交)级别:不管对方事务是否提交,只要数据发生改变本连接就可以察觉到
# 连接2:
mysql> commit; 提交事务
- read committed(读已提交)级别
# 连接1:
mysql> exit # 退出重新登录
Bye
PS C:\Users\Administrator> mysql -uroot -p123456mysql> use mydb17_transcationmysql> set autocommit = 0;mysql> set session transaction isolation level read committed; # 重新设置隔离mysql> show variables like "transaction_isolation";mysql> select * from ac;
+----+--------+-------+
| id | name | money |
+----+--------+-------+
| 1 | 张三 | 5000 |
| 2 | 李四 | 2000 |
+----+--------+-------+
# 连接2:
mysql> exit # 退出重新登录
Bye
C:\Users\Administrator>mysql -uroot -p123456mysql> use mydb17_transcation;mysql> set autocommit = 0;mysql> set session transaction isolation level read committed;mysql> show variables like "transaction_isolation";mysql> begin;mysql> update ac set money=5000-1000 where name="张三"; # 转账mysql> update ac set money=2000+1000 where name="李四"; # 转账
# 连接1:
mysql> select * from ac;
+----+--------+-------+
| id | name | money |
+----+--------+-------+
| 1 | 张三 | 5000 |
| 2 | 李四 | 2000 |
+----+--------+-------+
# 此时查询由于连接2未提交,隔离策略为read committed(读已提交),所以在连接2未提交以前,连接1查询的结果不变
# 连接2:
mysql> commit; # 提交事务
# 连接1
mysql> select * from ac;
+----+--------+-------+
| id | name | money |
+----+--------+-------+
| 1 | 张三 | 4000 |
| 2 | 李四 | 3000 |
+----+--------+-------+
# 连接2事务提交后,连接1查询的数据发生变化
- repeatable read(可重复度)级别,该隔离级别为mysql的默认隔离级别,对某字段进行操作时,其他事务禁止操作该字段,所以总能保持读取数据的一致性
# 连接1:
mysql> exit # 退出重新登录
Bye
PS C:\Users\Administrator> mysql -uroot -p123456mysql> use mydb17_transcationmysql> set autocommit = 0;mysql> show variables like "transaction_isolation"; # 查看发现为默认级别
+-----------------------+-----------------+
| Variable_name | Value |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
# 连接2
mysql> exit # 退出重新登录
Bye
C:\Users\Administrator> mysql -uroot -p123456mysql> use mydb17_transcationmysql> set autocommit = 0;mysql> show variables like "transaction_isolation"; # 查看发现为默认级别
# 连接1
mysql> select * from ac;
+----+--------+-------+
| id | name | money |
+----+--------+-------+
| 1 | 张三 | 4000 |
| 2 | 李四 | 3000 |
+----+--------+-------+
2 rows in set (0.00 sec)mysql> begin; # 连接1开启事务mysql> update ac set money=4000-1000 where name="张三"; # 模拟转账mysql> update ac set money=3000+1000 where name="李四"; # 模拟转账# 注意此时连接1的事务还未提交
# 连接2
mysql> begin;mysql> select * from ac;
+----+--------+-------+
| id | name | money |
+----+--------+-------+
| 1 | 张三 | 4000 |
| 2 | 李四 | 3000 |
+----+--------+-------+
2 rows in set (0.00 sec)mysql> update ac set money=8000 where name="张三";
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction# 等待.....之后上例报错:超过锁定等待超时;尝试重新启动事务
# 由于连接1的update事务未提交,此时会给表增加update语句独占锁,此时其他事务对持有独占锁的记录进行修改时,就会被阻塞
# 连接1
mysql> commit; # 提交事务
# 连接2
mysql> update ac set money=8000 where name="张三"; # 执行成功
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
- serializable(串行化)级别,该隔离模式下执行的事务在对某表进行操作期间,禁止其他所有事务对该表进行任何操作,如果强行操作也会报错(和上述错误一致),因为serializable隔离级别并发性能太低,用的相对比较少
5.9 总结
MySQL是一个客户端/服务器,对于同一个服务器来说,允许若干个客户端进行连接,每个客户端与服务器连接上后,就可以称为一个会话(Session)。
每个客户端都可以在自己的会话中向服务器发送请求语句,服务器可能同时处理多个事务
事务有隔离性的特性,理论上在某个事务对某个数据进行访问时,其他事务应该进行排队,当该事务提交之后,其他事务才可以继续访问这个数据。
但这样对性能影响太大,我们即想保持事务隔离性,又想让服务器在处理访问同一数据的多个事务性能尽量高些,那就看二者如何权衡取舍了。
四种隔离的并发性能如图: