mysql事务
事务是指一个完整的业务逻辑。A向B转账时,A-100;B+100,这是一个完整的业务逻辑,是最小的工作单元,要么同时成功,要么同时失败,不可再分,如此才能保证正确性。
只有DML语句才有事务概念,其他的与事务无关
insert\delete\update
涉及到数据的增删改,数据的安全就需要考虑,就需要使用事务。
事务实现机制
InnoDB提供了一组用来记录事务性活动的日志文件。
事务开启了:
insert
insert
delete
delete
事务结束了;
在事务的执行过程中,每一条DML的操作都会记录到事务性活动的日志文件中,在事务的执行过程中,我们可以提交事务,也可以回滚事务。
当一个事务提交时,innodb会清空事务性活动的日志文件,将数据全部持久化到数据库表中,提交事务标志着事务的结束,并且是一种全部成功的结束。
回滚事务是将之前的dml操作全部撤销,并且清空事务性活动的日志文件,回滚事务标志着事务的结束,是一种全部失败的结束。
如何提交事务与回滚事务?
提交事务:commit
回滚事务:rollback
事务:transaction
mysql默认情况下是支持自动提交事务,每执行一次DML语句,自动提交一次,回滚永远只能回滚到上一次的提交点。
那如何关闭mysql的事务自动提交机制?
start transaction;
使用以上的命令,就可以手动控制事务的提交以及回滚。
事务四个特性
A:原子性,事务是最小的工作单元,不可再分
C:一致性,所有的事务要求,在一个事务当中,所有的操作要么同时成功,要么同时失败,以保证数据的一致性
I:隔离性,A事务和B事务之间具有一定的隔离,A事务在操作一张表时,B事务也操作一张表时会如何?多线程访问同一张表时。
D:持久性,事务最终结束的一个保障,事务提交,就相当于把没有保存在硬盘上的数据保存在硬盘上。
重点研究一下隔离性
隔离性有强弱之分,就是隔离级别。
4个隔离级别
读未提交:read uncommitted(最低的隔离级别)
事务A可以读取到事务B未提交的数据,这种隔离级别存在脏读现象,dirty read,称为读到了脏数据。此种隔离级别都是理论上的,基本没人用。
读已提交:read committed
事务A只能读取到事务B提交之后的数据,这种隔离级别解决了脏读现象,这种隔离级别存在不可重复读取数据,例如事务开启之后,第一次读取的数据是3条,当前事务还没结束,可能第二次再读取的时候,读到的数据是4条,3不等于4称为不可重复读。这种隔离级别是比较真实的数据,每一次读到的数据是绝对的真实。
可重复读:repeatable read
事务A开启之后,不管是多久,每一次在事务A中读取到的数据是一致的,即使事务B已经修改了数据并且提交了,事务A读取到的数据还是没有发生改变,读取到的数据前后一致,这就是可重复读。可重复读的问题,可能存在数据的幻影,每一次读取到的数据都是幻想,不够真实。mysql中的默认隔离级别就是可重复读。
序列化/串行化:serializable(最高的隔离级别)
最高隔离级别,效率最低,解决了所有的问题,这种各级级别表示事务排队,不能并发。在这种隔离级别下,一个事务想要运行,必须要等到前面的事务处理完成才能开启。
验证以上四个事务隔离级别
read uncommitted
1.创建一张表
mysql> create table t_user(name varchar(255));
Query OK, 0 rows affected (0.03 sec)
mysql> select * from t_user;
Empty set (0.00 sec)
同时打开两个终端
查看事务隔离级别
mysql5.0版本是这样查的
mysql> select @@tx_isolation;
ERROR 1193 (HY000): Unknown system variable 'tx_isolation'
8.0版本命令
mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ |
+-------------------------+
1 row in set (0.00 sec)
查出当前的默认隔离级别为可重复读
设置隔离级别
mysql> set global transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ |
+-------------------------+
1 row in set (0.00 sec)
发现仍然是可重复读,此时退出数据库重新进入
mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| READ-UNCOMMITTED |
+-------------------------+
1 row in set (0.00 sec)
再次查看当前事务隔离级别,发现已经修改。
两个终端同时开启事务
在终端A中插入一条数据
在终端B中查看数据
发现,事务还没有提交,另一个地方就可以看到数据了,假如此时A终端rollback了,B终端恰好将数据取出来拿到程序中,那就出现了脏读现象。
read committe
接下来切换read committed验证
mysql> set global transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)
退出mysql重新登录
mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| READ-COMMITTED |
+-------------------------+
事务隔离等级已经修改完成
还是两个终端打开事务,A终端插入一条数据“lisi”
A终端的命令如下
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t_user values("lisi");
Query OK, 1 row affected (0.01 sec)
mysql> select * from t_user;
+----------+
| name |
+----------+
| zhangsan |
| lisi |
+----------+
B终端查看表
mysql> select * from t_user;
+----------+
| name |
+----------+
| zhangsan |
+----------+
1 row in set (0.00 sec)
发现lisi不能读取到!与刚才的read uncommitted不一样。
A终端提交事务!
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
mysql> select * from t_user;
+----------+
| name |
+----------+
| zhangsan |
| lisi |
+----------+
2 rows in set (0.00 sec)
B终端再次查表
mysql> select * from t_user;
+----------+
| name |
+----------+
| zhangsan |
| lisi |
+----------+
2 rows in set (0.00 sec)
当事务提交以后,就可以看得到A终端提交的数据,如此解决了脏读的发生。
repeatable read
修改事务隔离等级
mysql> set global transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ |
+-------------------------+
1 row in set (0.00 sec)
查看表中数据
mysql> select * from t_user;
+----------+
| name |
+----------+
| zhangsan |
| lisi |
+----------+
2 rows in set (0.00 sec)
A、B终端同时开启事务
start transaction;
A终端插入三条数据
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t_user values("abc");
Query OK, 1 row affected (0.00 sec)
mysql> insert into t_user values("abc");
Query OK, 1 row affected (0.00 sec)
mysql> insert into t_user values("abc");
Query OK, 1 row affected (0.00 sec)
mysql> select * from t_user;
+----------+
| name |
+----------+
| zhangsan |
| lisi |
| abc |
| abc |
| abc |
+----------+
5 rows in set (0.00 sec)
B终端查看表中数据
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t_user;
+----------+
| name |
+----------+
| zhangsan |
| lisi |
+----------+
2 rows in set (0.00 sec)
A终端提交事务
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
B终端查看表
mysql> select * from t_user;
+----------+
| name |
+----------+
| zhangsan |
| lisi |
+----------+
2 rows in set (0.00 sec)
发现当A中的事务提交后,B中查到的数据是在A事务完成前的表中数据,也就是说B的事务查到的是数据是一个A事务发生前版本的数据。
将B中的事务提交后查看表
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t_user;
+----------+
| name |
+----------+
| zhangsan |
| lisi |
| abc |
| abc |
| abc |
+----------+
5 rows in set (0.00 sec)
发现当A中的事务提交后,B中查到的数据是在A事务完成前的表中数据,也就是说B的事务查到的是数据是一个A事务发生前版本的数据。
将B中的事务提交后查看表
```sql
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t_user;
+----------+
| name |
+----------+
| zhangsan |
| lisi |
| abc |
| abc |
| abc |
+----------+
5 rows in set (0.00 sec)
至此三种事务隔离性就全部验证完成,最后一种事务隔离级别是阻塞的,当一个事务正在执行时,另一个事务会阻塞住,终点掌握可重复读、读已提交。