事务就是一组DML语句组成
,这些语句在逻辑上存在相关性,这一组DML语句要么全部成功,要么全部失败,是一个整体(原子性)。例如买票操作,上层看来是一个单纯的买票操作,但是下层却需要一条或多条SQL语句来完成转账操作,这一组SQL是一个整体,被称为事务。事务还规定不同的客户端看到的数据是不相同的。
一个 MySQL 数据库,可不止你一个事务在运行,同一时刻,有大量的请求被包装成事务,向 MySQL 服务器发起事务处理请求。如果多名用户都访问同样的表数据,在不加保护的情况,绝对会出现问题。并且事务由多条 SQL 构成,也会存在执行到一半出错或者不想再执行的情况,那么就会出现问题。
所以一个完整的事务,绝对不是简单的 sql 集合,还需要满足如下四个属性:
原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。 原子性、隔离性、持久性是因,一致性是果。
隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交( Read uncommitted )、读提交( read committed )、可重复读( repeatable read )和串行化( Serializable )
持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
上面四个属性,可以简称为 ACID
原子性(Atomicity,或称不可分割性)
一致性(Consistency)
隔离性(Isolation,又称独立性)
持久性(Durability)。
事务本质上是为了应用层服务的.而不是伴随着数据库系统天生就有的;上层使用者不需要关系各种潜在问题,一个完整的操作要么提交要么回滚。
在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务, MyISAM 不支持
在linux下,可以通过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
*************************** 5. row ***************************
Engine: MyISAM
Support: YES
Comment: MyISAM storage engine
Transactions: NO--不支持
XA: NO
Savepoints: NO
查看提交方式&&修改提交方式
--查看事务的提交方式
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |---ON代表自动提交
+---------------+-------+
1 row in set (0.03 sec)
mysql> set autocommit=0;--设置为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;--设置为1代表开启自动提交
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
第一步:将MySQL默认隔离级别设置为 读未提交
set global transaction isolation level READ UNCOMMITTED;
第二步:重启linux终端并查看
mysql> select @@tx_isolation;
+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED |
+------------------+
第三步:创建测试表
mysql> 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;
终端1
mysql> start transaction;--启动事务
Query OK, 0 rows affected (0.00 sec)
mysql> savepoint s1;--设置保存点s1
Query OK, 0 rows affected (0.00 sec)
mysql> insert into account values(1,'张三',1234.5);
Query OK, 1 row affected (0.01 sec)
mysql> savepoint s2;--设置保存点s2
Query OK, 0 rows affected (0.00 sec)
mysql> insert into account values(2,'李四',1234.6);
Query OK, 1 row affected (0.00 sec)
mysql> savepoint s3;--设置保存点s3
Query OK, 0 rows affected (0.00 sec)
mysql> insert into account values(3,'王五',1234.7);
Query OK, 1 row affected (0.00 sec)
mysql> rollback to s3;--回滚到s3,即插入的王五记录不存在
终端2
mysql> select * from account;
Empty set (0.00 sec)
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 1234.50 |
+----+--------+---------+
1 row in set (0.00 sec)
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 1234.50 |
| 2 | 李四 | 4444.50 |
+----+--------+---------+
2 rows in set (0.00 sec)
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 1234.50 |
| 2 | 李四 | 4444.50 |
| 3 | 王五 | 5555.50 |
+----+--------+---------+
3 rows in set (0.00 sec)
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 1234.50 |
| 2 | 李四 | 4444.50 |
+----+--------+---------+
2 rows in set (0.00 sec)
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 1234.50 |
+----+--------+---------+
1 row in set (0.00 sec)
mysql> select * from account;
Empty set (0.00 sec)
回滚:
rollback to s3--回滚到s3
rollback --回滚到最开始,即开启事务的状态
事务提交之后,此时的数据永久化保存在数据库里了。再回滚也不会影响到数据库;也就是回滚只能在事务运行进行的期间,事务提交之后,无法回滚
1. ctrl + \ 异常终止MySQL:自动回滚,与提交方式无关
终端1
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 1234.50 |
| 2 | 李四 | 1234.60 |
+----+--------+---------+
2 rows in set (0.00 sec)
mysql> begin;:
mysql> insert into account values(3,'王五',1234.7);
Query OK, 1 row affected (0.00 sec)
--ctrl+\断开连接
mysql> Aborted
终端2
mysql> begin;
--插入之后
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 1234.50 |
| 2 | 李四 | 1234.60 |
| 3 | 王五 | 1234.70 |
+----+--------+---------+
--断开之后
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 1234.50 |
| 2 | 李四 | 1234.60 |
+----+--------+---------+
只要把事务统一commit之后,这个数据就直接被插入到数据库中,并不会因为客户端崩溃这种情况而出现数据回滚
2. begin操作会自动更改提交方式,不会受MySQL是否自动提交影响
--查看数据
mysql> select * from account;
+----+--------+---------+
| id | name | blance |
+----+--------+---------+
| 1 | 张三 | 1234.50 |
| 2 | 李四 | 4444.50 |
| 3 | 王五 | 5555.50 |
| 4 | 赵六 | 6666.00 |
+----+--------+---------+
4 rows in set (0.00 sec)
--查看是否自动提交
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.01 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(5,'小刘',11111);
Query OK, 1 row affected (0.00 sec)
--查询
mysql> select * from account;
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 1234.50 |
| 2 | 李四 | 4444.50 |
| 3 | 王五 | 5555.50 |
| 4 | 赵六 | 6666.00 |
| 5 | 小刘 | 11111.00 |
+----+--------+----------+
5 rows in set (0.00 sec)
--异常终止
mysql> Aborted
当把自动提交关闭的时候,按照插入结果后断开连接,MySQL会自动回滚,与设置提交方式无关
3.单条 SQL 与事务的关系
启动事务后未commit,但是客户端崩了或者自己强行关闭客户端,MySQL会自动回滚
autocommit并不会影响begin后启动的事务的提交,使用begin后必须输入commit才能使数据持久化。
单句SQL本质就是事务,它的持久化和autocommit有关,autocommit为ON,表示单SQL无需手动commit自动提交,否则需要手动commit令数据持久化。(select有特殊情况,因为MySQL 有 MVCC )
事务可以手动回滚,同时,当操作异常,MySQL会自动回滚
我们能看到事务本身的原子性(rollback),持久性(commit)
MySQL服务可能会同时被多个客户端进程(线程)访问,访问的方式以事务方式进行。
一个事务可能由多条SQL构成,也就意味着,任何一个事务,都有执行前,执行中,执行后的阶段。而所谓的原子性,其实就是让用户层,要么看到执行前,要么看到执行后。执行中出现问题,可以随时回滚。所以单个事务,对用户表现出来的特性,就是原子性。
但是所有事务都要有个执行过程,那么在多个事务各自执行多个SQL的时候,就还是有可能会出现互相影响的情况。比如:多个事务同时访问同一张表,甚至同一行数据。
数据库中,为了保证事务执行过程中尽量不受干扰,就有了一个重要特征:隔离性 。
数据库中,允许事务受不同程度的干扰,就有了一种重要特征:隔离级别。
读未提交【Read Uncommitted】: 在该隔离级别,所有的事务都可以看到其他事务没有提交的执行结果。(实际生产中不可能使用这种隔离级别的),但是相当于没有任何隔离性,也会有很多并发问题,如脏读,幻读,不可重复读等,我们上面为了做实验方便,用的就是这个隔离性。
读提交【Read Committed】:该隔离级别是大多数数据库的默认的隔离级别(不是 MySQL 默认的)。它满足了隔离的简单定义:一个事务只能看到其他的已经提交的事务所做的改变。这种隔离级别会引起不可重复读,即一个事务执行时,如果多次 select,可能得到不同的结果。
可重复读【Repeatable Read】: 这是 MySQL 默认的隔离级别,它确保同一个事务,在执行中,多次读取操作数据时,会看到同样的数据行。但是有的数据库会有幻读问题。(MySQL不会)
串行化【Serializable】: 这是事务的最高隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决了幻读的问题。它在每个读的数据行上面加上共享锁,。但是可能会导致超时和锁竞争(这种隔离级别太极端,实际生产基本不使用)
在事务场景中,隔离是必要的。运行中的事务进行互相隔离。在事务运行中,“不会”出现互相干扰,这就是隔离性;根据影响程度的不同,隔离级别也不同。
--全局的隔离级别
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)
当前会话的session的隔离级别是根据全局global的隔离级别来的
--设置当前会话 /全局隔离级别语法
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ
COMMITTED | REPEATABLE READ | SERIALIZABLE}
--将当前session隔离级别级别设置为读提交
mysql> set session transaction isolation level read committed;
mysql> select @@global.tx_isolation;--全局级别没变化
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| READ-UNCOMMITTED |
+-----------------------+
mysql> select @@session.tx_isolation;--session级别改变
+------------------------+
| @@session.tx_isolation |
+------------------------+
| READ-COMMITTED |
+------------------------+
mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
将session隔离级别修改为读提交,其他用户的隔离级别是不会被修改的(来自全局),修改的仅仅是当前用户的的隔离级别。重新登录后,会话的隔离级别会按照全局隔离级别重新配置。
当一个会话修改了全局隔离级别,其他会话的全局隔离级别也会被改变,再次登录时,session和默认的隔离级别将会引用全局隔离级别的设置,因此尽量保证隔离级别一致性
--将当全局隔离级别级别设置为串行化
mysql> set global transaction isolation level SERIALIZABLE;
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| SERIALIZABLE |
+-----------------------+
mysql> select @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| READ-COMMITTED |
+------------------------+
mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
---- 退出登录后
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| SERIALIZABLE |
+-----------------------+
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重新登录一下让其生效
mysql> set global transaction isolation level read uncommitted;
多个并发的会话中启动事务,一个事务在修改数据库的时候还未commit提交,其他事务也是能够实时的查看到它修改后数据。
一个事务在执行中,读到另一个执行中事务的更新(或其他操作)但是未commit的数据,这种不合理的现象叫做脏读(dirty read)
读未提交的其他不合理现象还有不可重复读、幻读
读未提交几乎没有加锁,虽然效率高,但是问题太多,严重不建议采用
一个事务內读取结果不同
设置隔离级别并重启mysql
set global transaction isolation level read committed;
并发执行事务,一个事务对数据做了修改,操作期间另一个事务看不到,而一旦commit提交了,就能看到了。这就造成了其他事务在同一事务内,不同的时间点select查看数据库时,会查到不同的数据。这种现象叫不可重复读,事务中的读取不是原子的
同一个事物内读取结果相同
MySQL的默认隔离级别就是可重复读
同时启动两个事务,其中一个负责修改数据,另一个负责监控数据,当左边的终端修改数据的时候,未提交之前右边终端看不到,提交之后也看不到修改后数据;当右边事务提交之后才能看到,这就保证了一个事物在执行过程中不会看到不同的数据记录,称为可重复读
一般的数据库在可重复读情况的时候,无法屏蔽其他事务insert的数据,因为隔离性实现是对数据加锁完成的,而insert待插入的数据因为并不存在,那么一般加锁无法屏蔽这类问题,这会造成大部分内容虽然是可重复读的,但是insert的数据在可重复读情况被读取出来,导致多次查找时,会多查找出来新的记录,就如同产生了幻觉。这种现象,叫做幻读(phantom read,主要针对插入场景)。很明显,MySQL在RR级别的时候,是解决了幻读问题的(解决的方式是用Next-Key锁 (GAP+行锁)解决的。)
串行执行,一个事务提交之前,另一个事务阻塞
串行化就是对所有事务进行加锁,事务的执行全部挨个排队,这就导致了效率低下问题