目录
当然,不仅仅是select查询才会生成快照,还有其他生成快照的机制。
引言
本文将深入介绍mysql事务机制,以及对读未提交( Read uncommitted )、读提交( read committed )、可重复读( repeatable read )和串行化 ( Serializable )进行详细讲解。
下面通过一个示例引入:

什么是事务?
为什么会出现事务
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. 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
事务提交方式
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.41 sec)
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)
自动提交和手动提交的区别
自动提交和手动提交是数据库事务处理中的两种不同的提交方式,它们在何时将事务中的更改永久保存到数据库中有本质的区别:
自动提交(Auto-commit)
定义:自动提交模式是数据库系统默认的一种行为,在这种模式下,每执行一个SQL语句,数据库系统就会自动将其视为一个事务,并在语句成功执行后立即提交。
行为:在自动提交模式下,不需要程序员显示地开始或结束一个事务。每个SQL语句都是独立的,要么完全成功,要么在遇到错误时完全回滚。
适用场景:通常适用于简单的操作,或者对事务性要求不高的场景。
优点:简化了编程模型,因为程序员不需要关心事务的开始和结束。
缺点:在执行一系列相关操作时,不能保证操作的原子性,因为每个操作都是单独提交的。
手动提交(Manual-commit)
定义:在手动提交模式下,需要显式地开始一个事务,执行多个SQL语句,然后选择性地提交或回滚整个事务。
行为:使用BEGIN TRANSACTION(或相似语句)来开始一个事务,执行所需的操作,然后根据操作的成功与否,决定使用COMMIT来提交事务,或者使用ROLLBACK来回滚事务。
适用场景:适用于需要执行一系列相关操作,并且这些操作需要作为一个整体成功或失败的场景。
优点:可以保证一系列操作的原子性,一致性,隔离性和持久性(ACID属性)。
缺点:需要更多的编程工作来管理事务的开始和结束,不当的事务管理可能导致数据不一致的问题。
总结来说,自动提交和手动提交的主要区别在于事务的控制方式和对一系列操作原子性的保证。自动提交简化了单个操作的提交过程,而手动提交提供了更精细的事务控制,适用于需要保证多个操作整体性的复杂场景。
事务常见操作方式
简单银行用户表
提前准备
## Centos 7 云服务器,默认开启3306 mysqld服务
[xxx@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
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本质是一个客户端进程
[xxx@VM-0-3-centos ~]$ sudo netstat -ntp
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address 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,回滚在最开始
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account; -- 所有刚刚的记录没有了
Empty set (0.00 sec)
-- 终端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)
--终端 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)
-- 终端 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)
--实验一
-- 终端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)
结论:
事务隔离级别
隔离级别
查看与设置隔离性
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客户端,重新连接。
设置隔离级别时,分为全局和会话。
读未提交【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)
读提交【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之后,看到了!
--but,此时还在当前事务中,并未commit,那么就造成了,同一个事务内,同样的读取,在不同的时间段
(依旧还在事务操作中!),读取到了不同的值,这种现象叫做不可重复读(non reapeatable read)!!
(这个是问题吗??)
mysql> select *from account;
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 321.00 | --新的值
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
可重复读【Repeatable Read】
--终端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 | 李四 | 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,查看数据。
mysql> select * from account;
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
| 3 | 王五 | 5432.00 |
+----+--------+----------+
3 rows in set (0.00 sec)
--终端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; --多次查看,发现终端A在对应事务中insert的数据,在终端B的事
务周期中,也没有什么影响,也符合可重复的特点。但是,一般的数据库在可重复读情况的时候,无法屏蔽其
他事务insert的数据(为什么?因为隔离性实现是对数据加锁完成的,而insert待插入的数据因为并不存
在,那么一般加锁无法屏蔽这类问题),会造成虽然大部分内容是可重复读的,但是insert的数据在可重复读
情况被读取出来,导致多次查找时,会多查找出来新的记录,就如同产生了幻觉。这种现象,叫做幻读
(phantom read)。很明显,MySQL在RR级别的时候,是解决了幻读问题的(解决的方式是用Next-Key锁
(GAP+行锁)解决的。这块比较难,有兴趣同学了解一下)。
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
ysql> 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)
串行化【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)
总结:

如何理解隔离性(深度剖析)
什么是MVCC
MVCC(Multi-Version Concurrency Control,多版本并发控制)是一种用于数据库管理系统中的并发控制技术,它允许多个事务同时访问数据库中的数据,而不会相互干扰,从而提高系统的并发性能。MVCC通过保存数据对象的不同版本来实现这一目标,这样每个事务都可以看到数据库的一致性快照,即使其他事务正在修改数据。
以下是MVCC的一些关键点:
版本控制:当数据被修改时,MVCC不是直接覆盖旧数据,而是创建一个新的版本(或副本)来保存修改后的数据。旧版本的数据仍然保留,直到不再需要。
事务隔离:MVCC可以提供不同级别的事务隔离,例如可重复读(Repeatable Read)和快照隔离(Snapshot Isolation),这些隔离级别可以防止一些并发问题,如脏读、不可重复读和幻读。
无锁读取:由于读取操作可以访问数据的历史版本,所以在大多数情况下,读取操作不需要锁定数据,这大大减少了锁的竞争,提高了读取操作的并发性。
写操作管理:写操作通常需要额外的管理,以避免与读取操作冲突。这可能涉及到更新数据时创建新版本,以及适当时清除旧版本。
空间和时间开销:虽然MVCC提高了并发性,但它也可能导致额外的空间开销,因为需要存储多个版本的数据。此外,管理多个版本可能会增加CPU和存储的开销。
MVCC在多种数据库管理系统中都有实现,其中最著名的可能是PostgreSQL和Oracle数据库。例如,PostgreSQL使用MVCC来提供高并发性和事务隔离,而Oracle则通过其多版本读一致性(Multi-Version Read Consistency)机制来实现类似的功能。
MVCC是一种复杂的机制,但它为数据库系统提供了强大的并发控制能力,同时保持了良好的性能。
3个记录隐藏列字段
undo 日志


Read View
在InnoDB存储引擎中,Read view 的工作原理如下:
当一个事务开始执行一个一致性读(快照读)操作时,它会创建一个 Read view 。
Read view 会记录当前系统中所有活跃的事务ID(即未提交的事务)
在读取数据时,InnoDB会使用 Read view 来确定哪些版本的数据对当前事务是可见的。通常,如果一个数据版本的创建者的事务ID小于 Read view 中的最小活跃事务ID,或者该事务ID在 Read view 创建时已经提交,那么这个数据版本对当前事务是可见的。
一致性:事务看到的数据是一致的,不会看到其他事务部分完成的修改。
隔离性:事务之间的操作互不干扰,每个事务都好像是在独立的数据库快照上操作。
实现方式:数据库通过保存多个数据版本来实现快照。在InnoDB中,每当数据被修改时,旧版本的数据会被保留,直到没有任何事务需要访问它为止。
在MVCC中,快照机制通常与 Read view 结合使用,以实现以下隔离级别:READCOMMITTED:每个查询都会创建一个新的Read view,因此只能看到已经提交的数据。REPEATABLE READ(InnoDB的默认隔离级别):事务在第一次读取数据时创建一个 Read view,并在整个事务期间使用这个视图,从而实现可重复读。
当然,不仅仅是select查询才会生成快照,还有其他生成快照的机制。
在InnoDB存储引擎中,不仅是在执行SELECT语句时可能会生成read vew,而且在任何需要读取数据的操作中都可能生成read view。具体来说,以下几种情况会导致生成read view:
1.启动一个新的事务:当一个新的事务开始时,如果没有显式地指定隔离级别,那么默认情况下会使用REPEATABLE READ隔离级别,此时会生成-个read view。
2.执行SELECT语句:当执行一个SELECT语句时,如果该事务之前没有生成过read view,那么会生成一个新的read view.
3.其他DML操作:除了SELECT之外,UPDATE、DELETE等操作也可能导致生成read view,因为这些操作也需要读取数据来确定要更新的行。
在RR模式下,在本事务中读取的快照是begin开启本事务时截取的快照。
下面是 ReadView 结构,我们简化一下
class ReadView {
// 省略...
private:
/** 高水位,大于等于这个ID的事务均不可见*/
trx_id_t m_low_limit_id
/** 低水位:小于这个ID的事务均可见 */
trx_id_t m_up_limit_id;
/** 创建该 Read View 的事务ID*/
trx_id_t m_creator_trx_id;
/** 创建视图时的活跃事务id列表*/
ids_t m_ids;
/** 配合purge,标识该视图不需要小于m_low_limit_no的UNDO LOG,
* 如果其他视图也不需要,则可以删除小于m_low_limit_no的UNDO LOG*/
trx_id_t m_low_limit_no;
/** 标记视图是否被关闭*/
bool m_closed;
// 省略...
};
所以现在的问题就是,当前快照读,应不应该读到当前版本记录。一张图,解决所有问题!