一、事物的四个性质
- 原子性( Atomicity):一组操作要么全部执行,要么全部都不执行;如果一组操作中的一个操作执行失败,那么所有的操作都要回滚
- 一致性(Consistence):数据的状态在执行一组操作以后,数据状态在执行操作之前和执行操作之后应该是一致的。例子,假如账户A中有3000元,账户B中有2000元,那么在执行操作之前数据的状态是5000元,那么执行账户A向账户B转1000以后,A中的钱是2000,B中的钱是3000,账户的数据状态是5000和事务执行之前相同,因此,我们说这个事务是一致性的。
- 隔离性(Isolation):事物的隔离性表示事物之间相互影响的程度,主要有以下中程度:READ_UNCOMMITED、READ_COMMITED、REPEATABLE_READ、SERIALIZABLE;事物的隔离级别越高,系统越安全,但是系统的并发性能就会下降。
- 持久性(Durability):数据一旦执行了commit,那么就不能通过回滚操作撤销之前的操作。
二、事物的隔离性
2.1、事物的隔离性造成的不同的问题
问题 | 问题描述 |
---|---|
脏读 | 表示一个事务还没有提交,但是另一个事务就可以看到该事物没有提交的更新结果,这样就会造成如果该事物执行回滚,那么第二个事务在此之前看到的就是一个”脏数据”. |
不可重复读 | 同一个事务过程中对同一笔数据进行读取,每次读取的结果都不相同 |
幻读 | 在同一个事务中执行多次查询,多次查询的结果是不一样的。 |
这里需要注意的是,不可重复读和幻读之间的区别,幻读是因为另一个事物对数据表做删除或者插入数据,二不可重复读主要是侧重数据行中信息的修改在另一个事物中可见。
2.2、事务隔离级别
事务隔离级别 | 描述 | 安全性 | 并发性 |
---|---|---|---|
READ_UNCOMMITED | 一个事务可以读取另一个事物未提交的数据,这样会造成脏读、不可重复读、幻读的问题出现 | 极低 | 极高 |
READ_COMMITED | 一个事务的更新操作只能在提交以后,才能被其他的事物读取到。这样避免了脏读,无法避免幻读和不可重复读 | 低 | 高 |
READ_REPEATABLE | 保证在同一个事务过程中,同一笔数据读取的结果是相同的 | 高 | 低 |
SERIALIZABLE | 所有事务一次执行,不会出现脏读、不可重复读、幻读 | 极高 | 极低 |
从上面我们也可以看到事务的隔离级别和安全性是成正比的,但是和并发性是成反比的,所以,如果我们的系统在实现的时候需要衡量一下安全性和并发性。
三、局部事务与全局事务
局部事务
如果当前事务只有一个RM参与,那么我们就称这个事务为局部事务
全局事务
如果当前事务与多个RM参与,那么这个事务就是全局事务
四、实验
4.1、使用下面的语句查看Mysql的事物隔离级别
查看事务
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
设置事务隔离级别
mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
下面的实验的实验环境都是打开两个PowerShell中的窗口,并连接了MySQL的环境下执行的,所说的ClientA和ClientB 都是在两个PowerShell中执行的意思。
4.2、验证READ_ COMMITTED
第一步
Client A 使用下面的语句设置隔离级别
mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
Client B 设置事物隔离级别
mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
第二步
Client A 开启事务并执行一次查询
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select *from stu;
+----+------------+-----------+------------+---------+
| id | first_name | last_name | phone | address |
+----+------------+-----------+------------+---------+
| 1 | Ha | NULL | 1345678912 | HeNan |
| 2 | YU | Delaing | 1239877531 | HeNan |
| 3 | YU | Delaing | 1345678912 | HeNan |
| 4 | YU | Delaing | 1345678912 | HeNan |
| 5 | YU | Delaing | 1345678912 | HeNan |
| 6 | YU | Delaing | 1345678912 | HeNan |
+----+------------+-----------+------------+---------+
6 rows in set (0.00 sec)
ClientB 开启事务并执行一个Update操作
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> update stu set first_name='Ha' where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
第三步
Client A在ClientB没有提交事务之前执行一次查询,从结果我们可以看到,ClientA查询到了Client B 尚未提交的结果
mysql> select *from stu;
+----+------------+-----------+------------+---------+
| id | first_name | last_name | phone | address |
+----+------------+-----------+------------+---------+
| 1 | Ha | NULL | 1345678912 | HeNan |
| 2 | YU | Delaing | 1239877531 | HeNan |
| 3 | YU | Delaing | 1345678912 | HeNan |
| 4 | YU | Delaing | 1345678912 | HeNan |
| 5 | YU | Delaing | 1345678912 | HeNan |
| 6 | YU | Delaing | 1345678912 | HeNan |
+----+------------+-----------+------------+---------+
6 rows in set (0.00 sec)
总结:从实验我们就可以看出这里对于事务隔离属性如果设置为READ_UNCOMMITTED,那么就会发生脏读的情况。
4.3、验证READ_COMMITTED
第一步
Client A 使用下面的语句设置隔离级别
mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)
Client B 设置事物隔离级别
mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)
第二步
ClientA开启事务并执行一次查询
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select *from stu;
+----+------------+-----------+------------+---------+
| id | first_name | last_name | phone | address |
+----+------------+-----------+------------+---------+
| 1 | Yang | NULL | 1345678912 | HeNan |
| 2 | YU | Delaing | 1239877531 | HeNan |
| 3 | YU | Delaing | 1345678912 | HeNan |
| 4 | YU | Delaing | 1345678912 | HeNan |
| 5 | YU | Delaing | 1345678912 | HeNan |
| 6 | YU | Delaing | 1345678912 | HeNan |
+----+------------+-----------+------------+---------+
Client B 开启事务并执行一次update操作
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> update stu set last_name='Jiajiang' where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
第三步
Client A执行一条查询语句,从查询结果我们可以看到,Client A并没有查询到Client B修改且未提交的事物,所以在这种隔离级别下是不会发生脏读的。
mysql> select *from stu;
+----+------------+-----------+------------+---------+
| id | first_name | last_name | phone | address |
+----+------------+-----------+------------+---------+
| 1 | Yang | Qianhua | 1345678912 | HeNan |
| 2 | YU | Delaing | 1239877531 | HeNan |
| 3 | YU | Delaing | 1345678912 | HeNan |
| 4 | YU | Delaing | 1345678912 | HeNan |
| 5 | YU | Delaing | 1345678912 | HeNan |
| 6 | YU | Delaing | 1345678912 | HeNan |
+----+------------+-----------+------------+---------+
第四步
Client B 执行提交事务操作
mysql> commit;
Query OK, 0 rows affected (0.03 sec)
Client A 执行查询操作,从这里我们可以看到,在同一个事务中多次执行查询操作的结果因为另一个事物对表进行修改且提交了事务而导致不可重复读取同一行的数据,造成了不可重复读取的现象。
mysql> select *from stu;
+----+------------+-----------+------------+---------+
| id | first_name | last_name | phone | address |
+----+------------+-----------+------------+---------+
| 1 | Yang | Jiajiang | 1345678912 | HeNan |
| 2 | YU | Delaing | 1239877531 | HeNan |
| 3 | YU | Delaing | 1345678912 | HeNan |
| 4 | YU | Delaing | 1345678912 | HeNan |
| 5 | YU | Delaing | 1345678912 | HeNan |
| 6 | YU | Delaing | 1345678912 | HeNan |
+----+------------+-----------+------------+---------+
4.4、验证 REPEATABLE READ
第一步
Client A设置属性隔离级别
mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)
Client B设置属性隔离级别
mysql> set session transaction isolation level repeatable read;
Query OK, 0 rows affected (0.00 sec)
第二步
Client A执行开启事务并执行查询
mysql> select *from stu;
+----+------------+-----------+------------+---------+
| id | first_name | last_name | phone | address |
+----+------------+-----------+------------+---------+
| 1 | Yang | Jiajiang | 1 | HeNan |
| 2 | YU | Delaing | 1239877531 | HeNan |
| 3 | YU | Delaing | 1345678912 | HeNan |
| 4 | YU | Delaing | 1345678912 | HeNan |
| 5 | YU | Delaing | 1345678912 | HeNan |
| 6 | YU | Delaing | 1345678912 | HeNan |
+----+------------+-----------+------------+---------+
Client B 开启事务并执行update操作
mysql> update stu set address='AnHui' where id = 1;
第三步
Cliet B执行update后,我们在Client A执行查询操作发现我们Client A并没有读取到Client B修改的数据(在Client B没有提交事务之前)
mysql> select *from stu;
+----+------------+-----------+------------+---------+
| id | first_name | last_name | phone | address |
+----+------------+-----------+------------+---------+
| 1 | Yang | Jiajiang | 1 | HeNan |
| 2 | YU | Delaing | 1239877531 | HeNan |
| 3 | YU | Delaing | 1345678912 | HeNan |
| 4 | YU | Delaing | 1345678912 | HeNan |
| 5 | YU | Delaing | 1345678912 | HeNan |
| 6 | YU | Delaing | 1345678912 | HeNan |
+----+------------+-----------+------------+---------+
6 rows in set (0.00 sec)
第四步
在Client B中执行commit就会提交事务
mysql> commit;
Query OK, 0 rows affected (0.08 sec)
然后在ClientA中再一次执行查询,我们发现Client A中仍然不会读取到Client B修改的数据信息(在Client A 未提交的情况下)
mysql> select *from stu;
+----+------------+-----------+------------+---------+
| id | first_name | last_name | phone | address |
+----+------------+-----------+------------+---------+
| 1 | Yang | Jiajiang | 1 | HeNan |
| 2 | YU | Delaing | 1239877531 | HeNan |
| 3 | YU | Delaing | 1345678912 | HeNan |
| 4 | YU | Delaing | 1345678912 | HeNan |
| 5 | YU | Delaing | 1345678912 | HeNan |
| 6 | YU | Delaing | 1345678912 | HeNan |
+----+------------+-----------+------------+---------+
最后Client A提交事务以后
总结:我们发现REAPEATABLE_READ这个隔离级别避免了脏读和不可重复读取的问题。
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
4.5、验证Serializable
Client A 设置事务隔离级别和开启事务并执行一个查询操作,不提交事务。
mysql> set session transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select *from stu;
+----+------------+-----------+------------+---------+
| id | first_name | last_name | phone | address |
+----+------------+-----------+------------+---------+
| 1 | Yang | Jiajiang | 1 | AnHui |
| 2 | YU | Delaing | 1239877531 | HeNan |
| 3 | YU | Delaing | 1345678912 | HeNan |
| 4 | YU | Delaing | 1345678912 | HeNan |
| 5 | YU | Delaing | 1345678912 | HeNan |
| 6 | YU | Delaing | 1345678912 | HeNan |
+----+------------+-----------+------------+---------+
6 rows in set (0.00 sec)
Client B执行事务开启和查询update操作
mysql> set session transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> update stu set first_name where id = 2;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'where id = 2' at line 1
mysql> update stu set first_name='Li' where id = 2;
发现执行一直在等待,当等待一段时间以后出现下面的错误
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
结论:从这里我们可以发现如果事务隔离级别设置成了SERIALIZABLE,那么最终只能有一个事务执行,其他事务都需要等待当前事务执行完毕,否则将会出现超时等待而放弃执行事务。因此这种方式解决了所有的问题,没有脏读、不可重复读、幻读的情况。