多个事务同时操作同一个数据库的相同数据时,可能会导致事务的并发问题。
- 脏读:一个事务读取了其他事务还没有提交的数据,读到的是其他事务“更新”的数据。
- 不可重复读:一个事务多次读取,结果不一样。例如下订单,查询库存是1,另一个事务更新库存为100并提交事务,然后再去进行第二次的查询,查询结果却变成了100。
- 幻读:一个事务读取了其他事务还没有提交的数据,只是读到的是其他事务“插入”的数据。
可以通过设置隔离级别来解决并发问题。
read uncommitted
:读未提交。read committed
:读已提交。repeatable read
:可重复读。需要解决在当前事务查询已提交的结果不变,如果再重新开一个事务,才显示已提交的新的数据。这样就解决了一次事务多次读取的数据是一致的。serializable
:串行化。例如,淘宝搜索商品,搜索时暂时没有商品,等到后台加载完数据,其实是可以显示搜索结果的,但是因为串行化隔离性特别好,所以在当前事务,即使是刷新也无法显示,必须等到关闭浏览器重新打开,也就是重新打开一个新的事务才会显示。搜索商品要的是实时刷新,所以没有必要使用串行化隔离级别。
read uncommitted
(读未提交)
首先创建一个MySQL会话连接,并通过select @@tx_isolation;
查看当前隔离级别。默认的隔离级别为可重复读。
设置隔离级别为最低的read uncommitted
,此时事务可能出现脏读、不可重复读、幻读。
开启事务,将张三
修改为zhangsan
,但是并没有commit
提交事务。
重新开启一个MySQL会话连接,并将隔离级别也设置为read uncommitted
。
即使没有提交事务,但当从新的会话去查询account
表的数据时,张三
已经变成了zhangsan
如果之前的事务执行了回滚
再去查询数据时就会出现读取未被其它事务提交的数据,可能会出现脏读、不可重复读、幻读的问题。
read committed
(读已提交)
设置隔离级别为read committed
开启事务,将张三
修改为zhangsan
,但是并没有commit
提交事务。
在另一个新的会话设置隔离级别为read committed
,之前的事务没有提交,但是查询数据发现还是张三
,并没有变成zhangsan
。
之前的事务执行提交
另一个会话再去查询数据时,张三
变成了zhangsan
这样就避免了脏读,但是在一次事务多次查询中出现了不一样的结果,所以不能避免不可重复读和幻读。
repeatable read
(可重复读)
设置隔离级别为repeatable read
,开启事务,将张三
修改为zhangsan
,但是并没有commit
提交事务。
另一个新的会话也将隔离级别设置为repeatable read
,并查询数据,张三
并没有变成zhangsan
。
之前的事务执行提交
但是另一个会话执行查询命令,即使之前的事务提交,结果还是张三
,并没有变成zhangsan
只有当结束事务,重新开启一个新的事务进行查询时,才会查询到变更后的数据
这样就避免了脏读、不可重复读,但是不能避免幻读,比如下面就有可能出现幻读。
重新开启一个事务,将表中的所有username
都更新为aaa
,表中的数据此时只有两条,但是在点击回车的之前,有另一个会话执行了一个事务,这个事务在表中有插入了一条数据,然后再去执行之前的事务,会发现有3条记录被修改。这就产生了幻读。
serializable
(串行化)
将隔离级别设置为serializable
开启一个新的事务,将张三
修改为zhangsan
,但是并没有点击回车执行
此时在另一个会话执行插入数据操作,点击回车,此时会发现命令并没有执行,而是发生了阻塞,相当于在等待另外一个事务释放锁。
之前的事务执行提交命令,才会成功执行插入操作
这样序列化就避免了幻读,但是虽然序列化可以避免所有的并发问题,但是性能十分低下。