事务初探与MySQL事务隔离级别详解
事务初探
为啥要引入事务
事务是一个独立的工作单元
事务内的语句要么全部执行成功,要么全部失败
保持数据一致性
事务的ACID特性
事务拥有四个重要的特性(ACID):原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
原子性Atomicity
事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样
一致性Consistency
指事务将数据库从一种状态转变为另一种一致的的状态。事务开始前和结束后,数据库的完整性约束没有被破坏。例如工号带有唯一属性,如果经过一个修改工号的事务后,工号变的非唯一了,则表明一致性遭到了破坏。
隔离性Isolation
要求每个读写事务的对象对其他事务的操作对象能互相分离,即该事务提交前对其他事务不可见。 也可以理解为多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其它事务运行效果。这指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。例如一个用户在更新自己的个人信息的同时,是不能看到系统管理员也在更新该用户的个人信息(此时更新事务还未提交)。
注:MySQL 通过锁机制来保证事务的隔离性。
持久性Durability
事务一旦提交,则其结果就是永久性的。即使发生宕机的故障,数据库也能将数据恢复,也就是说事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。这只是从事务本身的角度来保证,排除 RDBMS(关系型数据库管理系统,例如 Oracle、MySQL 等)本身发生的故障。
注:MySQL 使用
redo log
来保证事务的持久性
MySQL事务的四种隔离级别
Refer: https://dev.mysql.com/doc/refman/5.7/en/innodb-transaction-isolation-levels.html
Trade-off:隔离级别越高,效率越低
隔离级别 | 脏读可能性 | 不可重复读可能性 | 幻读可能性 | 加锁读 |
---|---|---|---|---|
未提交读(Read uncommitted) | 可能 | 可能 | 可能 | 不可能 |
已提交读(Read committed) | 不可能 | 可能 | 可能 | 不可能 |
可重复读(Repeatable read) | 不可能 | 不可能 | 可能 | 不可能 |
可串行化(Serializable ) | 不可能 | 不可能 | 不可能 | 可能 |
MySQL中事务相关常用命令
显式开启事务
BEGIN; START TRANSACTION;
事务提交语句
COMMIT
事务回滚语句
ROLLBACK;
查看当前隔离级别
SHOW VARIABLES LIKE 'transaction_isolation'; select @@tx_isolation;
设置事务语句
SET [GLOBAL | SESSION] TRANSACTION transaction_characteristic [, transaction_characteristic] ... transaction_characteristic: { ISOLATION LEVEL level | access_mode } level: { REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED | SERIALIZABLE } access_mode: { READ WRITE | READ ONLY } set session transaction isolation level read committed;
实验相关数据库表
CREATE DATABASE trx_test;
USE trx_test;
CREATE TABLE test(
`id` bigint(20) unsigned NOT NULL,
`username` varchar(64) NOT NULL
) ENGINE=InnoDB;
INSERT INTO test VALUES (1, 'Tom');
READ UNCOMMITTED(读未提交)
- 在READ UNCOMMITTED隔离级别,事务中的修改即使没有提及,对其他事务也是可见的。
- 事务可以读取到未提交的数据,此现象也称之为脏读
- READ UNCOMMITTED这种级别一般都不会使用,而且任何操作都不会加锁
实验步骤
开启两个mysql终端,查看并设置当前session事务隔离级别为READ UNCOMMITTED
use trx_test; show variables like 'transaction_isolation'; set session transaction isolation level read uncommitted;
在console 1中开启一个事务,插入一条新数据
begin; insert into test values (2, 'LaoWang'); select * from test;
在console 2中开启一个事务后查看表中的数据
begin; select * from test; -- 可看到id = 2的数据
事务A | 事务B |
---|---|
设置事务隔离级别为READ UNCOMMITTED | 设置事务隔离级别为READ UNCOMMITTED |
开始事务A:begin; | |
insert into test values (2); | |
select * from test; | |
开始事务A:begin; | |
select * from test; |
READ COMMITTED(读提交)
一个事务开始时只能看到已经提交的事务所做的修改
这个级别也叫做不可重复读,因为执行多次同样的查询可能会得到不同的结果
大多数数据库的默认隔离级别为READ COMMITTED,但MySQL不是
实验步骤
开启两个mysql终端,查看并设置当前session事务隔离级别为READ COMMITTED
use trx_test; show variables like 'transaction_isolation'; set session transaction isolation level read committed;
在console 1中开启一个事务,插入一条新数据
begin; insert into test values (4, 'Leon');
在console 2中开启一个事务后查看表中的数据
begin; select * from test; -- 看不到id = 4的数据
在console 1中提交事务
commit;
此时在console 2中可以看到
select * from test; -- 看得到id = 4的数据
不可重复读表现在于在同一个事务之中,两个相同的查询得到的查询结果却不同
Console 1 | Console 2 |
---|---|
设置事务隔离级别为READ COMMITTED | 设置事务隔离级别为READ COMMITTED |
开始事务A:begin; |
|
插入一条新数据:insert into test values (4, 'Leon'); |
|
开始事务B:begin; |
|
查询数据:select * from test; (看不到id = 4的数据) |
|
事务 A 提交: commit; |
|
再次查询数据:select * from test; (id = 4的数据可见) |
|
事务 B提交:commit; |
REPEATABLE READ(可重复读)
可重复读保证了在同一事务中多次读取同样记录的结果是一致的
可重复读无法解决幻读问题
幻读指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新记录,当之前的事务再次读取时该范围的记录会产生幻行
可重复读是MySQL的默认事务隔离级别, InnoDB存储引擎通过多版本并发控制(MVCC)解决了幻读的问题
实验步骤
开启两个mysql终端,查看并设置当前session事务隔离级别为REPEATABLE READ
use trx_test; show variables like 'transaction_isolation'; set session transaction isolation level repeatable read;
在console 1中开启一个事务
begin; select * from test;
在console 2中开启一个事务后查看表中的数据
begin; select * from test;
在console 1中插入一条数据后提交事务
insert into test(id,username) values(10,'zhangsan'); commit;
此时在console 2中执行查询语句
select * from test;
Console 1 | Console 2 |
---|---|
设置事务隔离级别为REPEATABLE READ | 设置事务隔离级别为REPEATABLE READ |
开始事务A:begin; |
|
查询:select * from test; |
|
开始事务B:begin; |
|
查询数据:select * from test; (看不到id = 10的数据) |
|
插入一条数据insert into test(id,username) values(10,'zhangsan'); |
|
事务 A 提交: commit; |
|
再次查询数据:select * from test; (看不到id = 10的数据) |
注:关于幻读
MySQL使用next-key lock + MVVC解决幻读问题: https://dev.mysql.com/doc/refman/5.7/en/innodb-next-key-locking.html
为复现幻读,需要设置innodb_locks_unsafe_for_binlog
为1 (禁用Gap Lock)
在事务B中插入一条数据insert into test(id,username) values(10,'john');
会产生主键冲突
SERIALIZABLE(序列化)
- SERIALIZABLE是最高隔离级别,通过强制事务串行执行,避免了幻读问题
- SERIALIZABLE可简单理解为在读取每一行数据时都加锁
实验步骤
实验步骤
开启两个mysql终端,查看并设置当前session事务隔离级别为SERIALIZABLE
use trx_test; show variables like 'transaction_isolation'; set session transaction isolation level serializable;
在console 1中开启一个事务,插入一条数据
begin; insert into test(id, usename) values(15, 'LaoZhang');
在console 2中开启一个事务后查看表中的数据
begin; select * from test;
在console 1中提交事务
commit;
此时在console 2中会立即返回数据
Console 1 | Console 2 |
---|---|
设置事务隔离级别为SERIALIZABLE | 设置事务隔离级别为SERIALIZABLE |
开始事务A:begin; |
|
插入一条数据insert into test(id, usename) values(15, 'LaoZhang'); |
|
开始事务B:begin; |
|
查询数据:select * from test; (看不到任何数据,一直等待) |
|
事务 A 提交: commit; |
立即返回数据 |
相关文章
- supervisor使用教程
一、安装 1:easy_install 安装: easy_install supervisor 2:pip 安装: pip install supervisor 3:Debian / Ubuntu
- gitlab的ssh key不生效的问题
在用 gitlab 的管理代码时发现一个问题:如果用 http 协议,每次 push 的时候都需要输入用户名和密码,如果 用 ssh 协议,先要生成公钥: ssh-keygen -t rsa -C
- UOJ安装指南
这是一个UOJ的docker版本。在安装之前,请确认Docker已经安装在您的操作系统中。这个docker的映像是64位的版本,在32位的系统上安装可能会出现错误。 安装 请先下载 JDK7u76
- untu14.04下创建用户并赋予执行sudo命令的权限
untu14.04下创建用户并赋予执行sudo命令的权限 创建用户:adduser +用户名(该命令在home下生成用户目录并创建用户) 1. 切换到root用户下 2. /etc/sud
- 如何查看当前apache的工作模式prefork worker还是event模式?
查看apache工作模式 $ apachectl -V (注:apachectl可理解为apache control,其实是一段bash脚本) Server version: Apache/2.4.
随机推荐
- supervisor使用教程
一、安装 1:easy_install 安装: easy_install supervisor 2:pip 安装: pip install supervisor 3:Debian / Ubuntu
- gitlab的ssh key不生效的问题
在用 gitlab 的管理代码时发现一个问题:如果用 http 协议,每次 push 的时候都需要输入用户名和密码,如果 用 ssh 协议,先要生成公钥: ssh-keygen -t rsa -C
- UOJ安装指南
这是一个UOJ的docker版本。在安装之前,请确认Docker已经安装在您的操作系统中。这个docker的映像是64位的版本,在32位的系统上安装可能会出现错误。 安装 请先下载 JDK7u76
- untu14.04下创建用户并赋予执行sudo命令的权限
untu14.04下创建用户并赋予执行sudo命令的权限 创建用户:adduser +用户名(该命令在home下生成用户目录并创建用户) 1. 切换到root用户下 2. /etc/sud
- 如何查看当前apache的工作模式prefork worker还是event模式?
查看apache工作模式 $ apachectl -V (注:apachectl可理解为apache control,其实是一段bash脚本) Server version: Apache/2.4.