MySQL的隔离级别


mysql中的事务

事务:一个事务是一个完整的业务逻辑单元,不可再分,比如说银行的转账,A向B进行转账。需要执行两条update语句
    
update t_act set balance =balance-10000 where actno = 'tom';
update t_act set balance = balance+10000 where actno = 'jerry';

以上两条SQL语句必须要求同时成功或者是同时失败,不能够一条成功了,另外一条没有成功,保证数据的正确性和完整性。

要想保证上面的两条SQL同时成功或者是同时失败,那么必须使用到MySQL中的事务管理。

只有DML语句才和事务有关系,因为只有DML中的(insert、update、delete)语句都是和数据库中的事务保持关系的。

事务的存在就是为了保证数据的完整性。

如果所有的业务一条DML就能够搞定,那么就不再需要事务了。但是现实就是很复杂,没有办法做到一条SQL语句就能够做到。

现实生活中就是一个业务,关联着多个DML语句的。

有一个业务场景需要来进行完成:

需要先进行一条Insert语句,然后进行一条update语句,最后执行一条delete语句
    
    第一步:开启事务;
    第二步:进行insert语句,这条语句执行成功之后,并不是立即就修改掉硬盘中的数据,而是保存中数据库的操作历史中去;
    第三步:执行update语句,这条语句执行成功之后,并不是立即就修改掉硬盘中的数据,而是和上面一样,保存到操作历史中去;
    第四步:执行delete语句,这条语句执行成功之后,并不是立即就修改掉硬盘中的数据,而是保存到数据库的操作历史中去;
    第五步:只有上面的三条DML语句都成功的时候,这个时候提交事务之后,才会真正的修改掉硬盘中的数据,并将操作历史中的文件进行删除。
    
注意:如果要是有一条语句没有成功,那么将会回滚。最终的结果就是,不会去修改掉磁盘中的数据,同时也会将这些操作历史进行删除。
    不管是commit还是rollback,都会将事务进行关闭。
    commit比rollback多做了一步,那就是修改硬盘中的数据。

可以在事务中进行设置保存点,savePoint,在进行rollback的时候,可以设置回到哪个保存点,但是保存点这块很少使用,了解下。

MySQL事务又被称之为本地事务。

MySQL的四大特性

ACID
    A:Atomicity,原子性。事务是最小的工作单元,不可再进行分离。一组SQL组合起来,要么全部成功,要么全部失败
    
    C:Consistency,一致性。事务要么是成功的(commit),要么是失败的(rollback)。在DML语句中,会导致数据的状态从一种状态转换到另外一个状态上去。
        		  但是要求的是在事务开始到事务结束这期间,其它事务是看不到这一块的数据的。
        
	I:Isolation:隔离性。事务A和事务B具有隔离性,java开发这块难点就是事务和线程,也是为了保证数据的安全。
                 A干自己的事情,B干自己的事情。这块会影响到一致性。
        		
        隔离级别:
        		 但是隔离性破坏了一致性,在这个基础上衍生出来的。
        		 读未提交:读到了其它事务还没有提交的数据;存在问题是脏堵
        		 读已提交:读到了其它事务提交到的数据。存在问题是不可重复读
        		 可重复读:在进行select的时候,对于同一个SQL语句来说,要求读取到的结果必须是相同的。存在幻读
        		 串读:比如有十个事务在进行读取的时候,这些个事务必须排队,一个一个的执行。
        
	D:Durability:持久性。数据最终必须持久化到硬盘文件中去。保证数据是不会丢失的。

但是这块对隔离性又有了级别。

事务隔离性,四种:
    1、读未提交
    	对方事务还没有提交,当前事务可以读取到对方未提交的数据。
    	存在的问题:脏读,读到了脏的数据
    
    2、读已提交(Oracle的默认级别)
    	对方事务提交之后的数据可以读到。
        存在的问题:不可重复读取。对方已提交,读取的就是新的,读取不到最新的数据。
    			  可以理解是不同的时间节点,读取数据库中的记录条数是不固定的。一会儿是10条,一会儿是8条,一会儿是100条
    	解决的问题:解决了脏读现象。
    
    3、可重复读(MySQL的默认级别)
    	当前事务没有结束,读取的永远都是固定的条数,读取的永远都是开启事务时候的记录的条数,无论另外一个事务进行了怎样的操作。
    	首先是解决了上面的不可重复进行读取的问题。
    	存在的问题:幻读。因为另外一个事务可能对数据库中的记录进行了增删改,但是当前的事务读取到的还都是开启事务之前的数据
    			  相当于是在数据库做了一个备份。
    
    4、串读
       特点:当前事务没有结束之前,其他的事务不能够进行操作,只能够排队进行等待
       解决了幻读的现象,但是缺点是效率比较低。

MySQL事务的演示:

mysql默认的是自动提交的,也就是执行任意一条DML语句,那么自动进行提交,所以要进行演示的时候,需要手动的进行关闭。
TCL语句:commit和rollback。

关闭自动提交命令:start transaction,开启事务的意思。手动的来进行提交事务

准备工作:

drop table if exists t_user;
create table t_user(
	id int PRIMARY key auto_increment,
	username varchar(200)
);

实验一:

开启动作:begin;
insert into t_user (username) values ('liguang');
在输入命令rollback的时候,发现表中已经存在了对应的一条数据
    
    
mysql> insert into t_user (username) values ('liguang');
Query OK, 1 row affected (0.00 sec)

mysql> rollback;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t_user;
+----+----------+
| id | username |
+----+----------+
|  1 | liguang  |
+----+----------+

使用start transaction来关闭自动提交事务的机制,标志着一个事务开启了。

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into t_user (username) values ('zhangsan');
Query OK, 1 row affected (0.00 sec)

mysql> select * from t_user;
+----+----------+
| id | username |
+----+----------+
|  1 | liguang  |
|  2 | zhangsan |
+----+----------+
2 rows in set (0.00 sec)

mysql> insert into t_user (username) values ('wangwu');
Query OK, 1 row affected (0.00 sec)

mysql> select * from t_user;
+----+----------+
| id | username |
+----+----------+
|  1 | liguang  |
|  2 | zhangsan |
|  3 | wangwu   |
+----+----------+
3 rows in set (0.00 sec)

mysql> rollback;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from t_user;
+----+----------+
| id | username |
+----+----------+
|  1 | liguang  |
+----+----------+
1 row in set (0.00 sec)
------------------------------------------------------------------------
总结:在进行设置了开启了事务之后,再次进行了插入数据之后,虽然可以进行查询,但是在回滚了之后,发现数据库中的记录依然没有发生变化,依然是原来保存到数据库中的数据。
验证了原来的结论,rollback之后不会将操作历史数据同步到硬盘中去,而是直接给删除掉了。

用两个事务来演示事务的隔离级别:

第一:演示读未提交,开启两个窗口,代表的是两个连接,开启了两个事务。

// 设置隔离级别
set global transaction isolation level read uncommitted;
// 查看设置的隔离级别
select @@global.transaction_isolation;

mysql> set global transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)

mysql> select @@global.transaction_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| READ-UNCOMMITTED      |
+-----------------------+
1 row in set (0.00 sec)

设置完成窗口之后,将窗口关闭之后进行重新打开。

窗口一:

开启事务A
    首先开启事务
    mysql> start tranction;
    mysql> insert into t_user (username) values ('limeng');
	Query OK, 1 row affected (0.00 sec)
   // 不进行提交     
     。  
开启事务B:
        mysql> select * from t_user;
        +----+----------+
        | id | username |
        +----+----------+
        |  1 | liguang  |
        |  4 | limeng   |
        +----+----------+
        2 rows in set (0.00 sec)
            
在这里读去到了未提交的数据       

在事务A中开启事务后,然后插入一条对应的数据,然后开启事务B,执行查询操作,可以看到查询出来的值为事务A插入进去的值;

演示二:读取已提交的数据

开启事务A窗口:
	// 设置隔离几倍
    set global transaction isolation level read committed;
    // 查看设置的隔离级别
    select @@global.transaction_isolation;   	
开启事务B窗口:
    // 设置隔离几倍
    set global transaction isolation level read committed;
    // 查看设置的隔离级别
    select @@global.transaction_isolation;    

下面是具体的代码

// 事务A插入数据,并进行了提交。可以先演示没有commit之后,事务B能否 读取到事务A未提交的数据。不能。
mysql> select * from t_user;
+----+----------+
| id | username |
+----+----------+
|  1 | liguang  |
+----+----------+
1 row in set (0.00 sec)

mysql> insert into t_user (username) values ('lijiayi');
Query OK, 1 row affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

在事务B中:

mysql> select * from t_user;
+----+----------+
| id | username |
+----+----------+
|  1 | liguang  |
|  5 | lijiayi  |
+----+----------+
2 rows in set (0.00 sec)
---------------------------------------------------------------------
可以在事务B中查看得到数据库中的信息

演示第三种:可重复读

在事务A窗口中
    开启事务A窗口:
	// 设置隔离几倍
    set global transaction isolation level repeatable read;
    // 查看设置的隔离级别
    select @@global.transaction_isolation; 
在事务B窗口中:
    开启事务A窗口:
	// 设置隔离几倍
    set global transaction isolation level repeatable read;
    // 查看设置的隔离级别
    select @@global.transaction_isolation; 

在事务A窗口中:

mysql> select * from t_user;
+----+-------------+
| id | username    |
+----+-------------+
|  1 | liguang     |
|  5 | lijiayi     |
|  6 | lining      |
|  7 | linxiaoixao |
+----+-------------+
4 rows in set (0.00 sec)
// 把表都来进行删除
mysql> insert into t_user (username) values ('lijing');
Query OK, 1 row affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)
-------------------------------------------------------------
mysql> select * from t_user;
+----+-------------+
| id | username    |
+----+-------------+
|  1 | liguang     |
|  5 | lijiayi     |
|  6 | lining      |
|  7 | linxiaoixao |
|  8 | lijing      |
+----+-------------+
5 rows in set (0.00 sec)
    
    

在事务B窗口中:

mysql> select * from t_user;
+----+-------------+
| id | username    |
+----+-------------+
|  1 | liguang     |
|  5 | lijiayi     |
|  6 | lining      |
|  7 | linxiaoixao |
+----+-------------+
4 rows in set (0.00 sec)
-------------------------------------------------
一直重复进行去读,但是还是没有读到最新的数据。    

演示四:串读

在事务A窗口中
    开启事务A窗口:
	// 设置隔离几倍
    set global transaction isolation level serializable;
    // 查看设置的隔离级别
    select @@global.tx_isolation; 
在事务B窗口中:
    开启事务A窗口:
	// 设置隔离几倍
    set global transaction isolation level serializable;
    // 查看设置的隔离级别
    select @@global.tx_isolation; 
在事务A中
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into t_user (username) values ('lixiang');
Query OK, 1 row affected (0.00 sec)

事务B中开启事务
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t_user;


在事务A中没有进行提交或者是回滚,在事务B中进行查询的时候,事务B会一直卡住,无法进行操作。

MySQL中的事务开启的两种方式:

MySQL事务的开启与提交,autocommit自动提交功能
对于一个MySQL数据库(InnoDB),事务的开启与提交模式无非下面这两种情况:

1、若参数autocommit=0,事务则在用户本次对数据进行操作时自动开启,在用户执行commit命令时提交,用户本次对数据库开始进行操作到用户执行commit命令之间的一系列操作为一个完整的事务周期。
若不执行commit命令,系统则默认事务回滚。总而言之,当前情况下事务的状态是需要手动去提交。

2、若参数autocommit=1(系统默认值),事务的开启与提交又分为两种状态:

2.1、手动提交:autocommit=0
当用户执行start transaction命令时(事务初始化),一个事务开启,当用户执行commit命令时当前事务提交。
从用户执行start transaction命令到用户执行commit命令之间的一系列操作为一个完整的事务周期。
若不执行commit命令,系统则默认事务回滚。

2.2、自动提交:autocommit=1
如果用户在当前情况下(参数autocommit=1)未执行start transaction命令而对数据库进行了操作,系统则默认用户对数据库的每一个操作为一个孤立的事务,
也就是说用户每进行一次操作系都会即时提交或者即时回滚。这种情况下用户的每一个操作都是一个完整的事务周期。

=================================

设置autocommit开启和关闭的方法:
用户可以将自动提交功能强制置为OFF。这样用户执行SQL语句后将不会被提交了,而执行COMMIT命令才提交,执行ROLLBACK命令回滚。

查看当前自动提交功能状况:
show variables like 'autocommit';  
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 row in set (0.06 sec)

临时生效设置方法:
下面是将自动提交功能置为ON以及置为OFF的具体语法。
将自动提交功能置为ON
SET AUTOCOMMIT=1;

将自动提交功能置为OFF
SET AUTOCOMMIT=0;

永久生效设置方法:
通过修改配置文件my.cnf文件,通过vim编辑my.cnf文件,在[mysqld](服务器选项下)添加:
autocommit=0

参考博客: