目录
不正常操作--证明commit了,客户端崩溃,但是数据不会被影响
不正常操作--证明begin操作会自动更改提交方式,不受MySQL是否自动提交的影响
[读未提交(Read Uncommitted):](#读未提交(Read Uncommitted):)
[读提交(Read Committed):](#读提交(Read Committed):)
[可重复读(Repeatable Read):](#可重复读(Repeatable Read):)
前言:
如果CURD不加控制,会有什么问题?
CURD满足什么属性,能解决上述问题?
- 买票的过程必须是原子的吧
- 买票互相应该不能影响
- 买好票应该要永久有效吧
- 买前,买后的状态应该要很明确的
什么是事务?
事务就是一组DML语句构成,这些语句在逻辑上存在相关性,这一组DML语句要么全部成功,要么全部失败,是一个整体。MySQL提供一种机制,保证我们达到这样的效果。事务还规定不同的客户端看到的数据是不同的。
事务主要用在处理操作量大,复杂度高的数据。好比转账,你要将钱从手机上转钱给另外一个人,这时候就会有一堆MySQL语句:查询,修改等待,这样一堆的SQL语句加起来构成一个事务
正如我们上面所说,一个 MySQL 数据库,可不止你一个事务在运行,同一时刻,甚至有大量的请求被包装成事务,在向 MySQL 服务器发起事务处理请求。而每条事务至少一条 SQL ,最多很多 SQL ,这样如果大家都访问同样的表数据,在不加保护的情况,就绝对会出现问题。甚至,因为事务由多条 SQL 构成,那么,也会存在执行到一半出错或者不想再执行的情况,那么已经执行的怎么办呢?
所以一个完整的事务,绝对不是简单的sql集合,还要满足以下四个属性:
- 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样
- **一致性:**在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作
- 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交( Readuncommitted )、读提交( read committed )、可重复读( repeatable read )和串行化( Serializable )
- **持久性:**事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失
上面四个属性简称为ACID
- 原子性(Atomicity)
- 一致性(Consistency)
- 隔离性(lsolation)
- 持久性(Durability)
事务的版本支持
在MySQL中只有使用了Innodb数据库引擎的数据库或表才支持事务,MyISAM不支持
- Transactions:YES 代表支持事务
- Savepoints:YES 代表支持事务保存点
sql
SET AUTOCOMMIT=0 # SET AUTOCOMMIT=0 禁止自动提交 1就是开启自动提交
事务的提交方式
常见的提交方式有两种:
- 自动提交
- 手动提交
查看事务的提交方式:
sql
show variables like 'autocommit'
用SET来改变MySQL的自动提交模式:
sql
SET AUTOCOMMIT=0; #SET AUTOCOMMIT=0 禁止自动提交 1就是自动提交
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | OFF |
+---------------+-------+
事务的常见操作
- 创建测试表:
sql
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;
正常操作--演示证明事务回滚
cpp
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)
不正常操作--证明未commit,客户端崩溃,事务自动回滚
这里设置的隔离级别是读未提交:
sql
-- 终端A
mysql> select * from account; -- 当前表内无数据
Empty set (0.00 sec)
+---------------+-------+
| 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> select * from account; --终端A崩溃前
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 张三 | 100.00 |
+----+--------+--------+
1 row in set (0.00 sec)
mysql> Aborted -- ctrl + \ 异常终止MySQL--终端B
mysql> select * from account; --数据自动回滚
Empty set (0.00 sec)
不正常操作--证明commit了,客户端崩溃,但是数据不会被影响
sql
--终端 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)
不正常操作--证明begin操作会自动更改提交方式,不受MySQL是否自动提交的影响
sql
-- 终端 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的表现:
sql
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)
结论:
- 只要输入begin或者start transaction,事务必须要通过commit提交才会持久化,跟是否设置set autocommit无关
- 事务可以手动回滚,同时,当操作异常,MySQL会自动回滚
- 对InnoDB每一条SQL语言都默认封装成事务,自动提交(select特殊)]
- 从上面的例子可以看出事务的原子性(回滚)、持久化(commit)
注意事项:
- 如果没有设置保存点,也可以回滚,只能回滚到事务的开始。直接使用 rollback(前提是事务
还没有提交) - 如果一个事务被提交了(commit),则不可以回退(rollback)
- 可以选择回退到哪个保存点
- InnoDB 支持事务, MyISAM 不支持事务
- 开始事务可以使 start transaction 或者 begin
事务隔离级别
如何理解隔离性:
- MySQL服务可能会同时被多个客户端进程(线程访问),访问的方式以事务方式进行
- 一个事务可能由多条SQL构成,也就意味着,任何一个事务,都有执行前,执行中,执行后的阶段。而所谓的原子性,其实就是让用户层,要么看到执行前,要么看到执行后。执行中出现问题,可以随时回滚。所以单个事务,对用户表现出来的特性,就是原子性。
- 但,毕竟所有事务都要有个执行过程,那么在多个事务各自执行多个SQL的时候,就还是有可能会出现互相影响的情况。比如:多个事务同时访问同一张表,甚至同一行数据。
- 数据库中,为了保证事务在执行的过程中尽量不受干扰,就有了一个重要特征:隔离性
- 数据库中,允许事务收到不同程度的干扰--隔离级别
隔离级别:
- 读未提交 (Read Uncommitted) : 在该隔离级别,所有的事务都可以看到其他事务没有提交的执行结果。(实际生产中不可能 使用这种隔离级别的),但是相当于没有任何隔离性,也会有很多并发问题,如脏读,幻读,不可重复读等,我们上面为了做实验方便,用的就是这个隔离性。
- 读提交(Read Committed) :该隔离级别是大多数数据库的默认的隔离级别 (不是 MySQL 默认的)。它满足了隔离的简单定义:一个事务只能看到其他的已经提交的事务所做的改变。这种隔离级别会引起不可重复读 ,即一个事务执行时,如果多次 select, 可能得到不同的结果。
- 可重复读(Repeatable Read): 这是 MySQL 默认的隔离级别,它确保同一个事务,在执行中,多次读取操作数据时,会看到同样的数据行。但是会有幻读问题。
- **串行化【Serializable】:**这是事务的最高隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决了幻读的问题。它在每个读的数据行上面加上共享锁。但是可能会导致超时和锁竞争(这种隔离级别太极端,实际生产基本不使用)
隔离级别如何实现:隔离基本都是通过锁来实现的,不同的隔离级别用不同的锁。常见的:表锁,行锁,读锁,写锁,间隙锁,Next_Key锁等。
查看和设置隔离性:
sql
-- 查看
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)
- 查看全局的隔离级别
- select @@global.tx_isolation;
- 查看当前会话的隔离级别
- select @@session.tx_isolation;
- select @@tx_isolation;
设置隔离性:
语法:
sql
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL
{READ UNCOMMITTED | READCOMMITTED | REPEATABLE READ | SERIALIZABLE}
设置了当前会话隔离性并不会影响新的会话的隔离性,但设置全聚德就会影响了
读未提交(Read Uncommitted):
说明:几乎没有加锁,虽然效率高,但是问题太多,严重不建议采用
sql
set global transaction isolation level read uncommitted;
#重启客户端生效
在一个事务中未commit持久化事务之前,就会被另一个事务看到
读提交(Read Committed):
上一个的升级版,在一个事务中commit的事务才会被另外一方看到
可重复读(Repeatable Read):
在一般数据库中,无法屏蔽不同终端的INSERT进去的数据,也就是我在一个事务中连续的select一个表会出现不同的结果,这种select多出来的样子就像出现了幻觉,较多幻读,但是MySQL解决了这个问题 : [ 通过Next-Key锁(GAP+行锁)解决的 ]
这也是MySQL默认的隔离性。
串行化(Serializable):
对所有的操作都加锁,进行串行化,不会有问题,但是效率极其低下,几乎完全不会被采用
结论:
- 其中隔离级别越严格,安全性越高,但数据库的并发性能也就越低,往往需要在两者之间找一个平衡点。
- 不可重复读的重点是修改和删除:同样的条件, 你读取过的数据,再次读取出来发现值不一样了
幻读的重点在于新增:同样的条件, 第1次和第2次读出来的记录数不一样 - mysql默认的隔离级别是可重复读,一般情况不要乱改
- 事务也有长短事务这样的概念。事务间互相影响,指的是事务在并行执行的时候,即都没有commit的时候,影响会比较大
一致性(Consistency):
- 事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态。当数据库只包含事务成功提交的结果时,数据库处于一致性状态。如果系统运行发生中断,某个事务尚未完成而被迫中断,而改未完成的事务对数据库所做的修改已被写入数据库,此时数据库就处于一种不正确(不一致)的状态。因此一致性是通过原子性来保证的。
- 其实一致性和用户的业务逻辑强相关,一般MySQL提供技术支持,但是一致性还是要用户业务逻辑做支撑,也就是,一致性,是由用户决定的。
- 而技术上,通过AID保证C