1-并发事务导致的问题
假设现在有A,B两个事务,这两个事务都可以对数据库进行读取和修改。那么,排列组合后可以分为三种情况(都是以操作同一资源为前提):
情况 | 操作 | 结果 |
---|---|---|
1 | A,B都采取读操作 | 不会出现任何问题 |
2 | 其中一个事务进行修改,另一个则进行读取 | 执行修改的那个事务不会出错,执行读操作的事务读取的数据会出现问题(称为读问题,下面会详细介绍) |
3 | A,B都对数据库进行修改 | 在对同一数据进行修改时,先修改的数据会被后修改的数据覆盖(称为更新问题,下面会详细介绍) |
1.1读问题
读问题分为三类(我们假定执行读操作的事务为A,执行修改的事务为B):
问题名称 | 解释 |
---|---|
脏读 | 即A在B还未提交数据时读取数据。因为你不知道B事物之后执行的是回滚还是提交,所以你不能保证此数据的可靠性。 |
不可重复读 | A在B提交数据前读取一次,然后在B提交数据后再读取一次。造成A事务内部多次读取的结果不一样。 |
幻读 | A 在B插入数据前读取一次,然后在B插入数据后再读取一次。造成A事务内部前后两次查询结果不一致。 |
1.2更新问题
以修改数据库中表的数据为例。A修改好表中的某一数据准备提交时,B立即修改此表中的其它项数据。A正常提交数据,然后B也正常提交数据。
时间点 | 事务A | 事务B |
---|---|---|
1 | 开启事务 | |
2 | 开启事务 | |
3 | update t_sutdent ts set ts.sname=“张三” where ts.sid=“10” | |
4 | update t_sutdent t set t.sname=“李四”,t.age=18 where ts.sid=“10” | |
5 | 提交 | |
6 | 提交 | |
问题 | 事务A认为数据库中的数据已成功改为:sid=10的学生的姓名为“张三”。(实际上是“李四”) | 事务B认为数据库已修改成功。(确实修改成功,但却覆盖了A的数据,且A并不知晓) |
解决方法:
-
使用排它锁(首句添加“for update”): 把写写并行改为写写串行(如下表)。简单地说就是A,B在执行数据修改时要排队。而且当某一进程正在修改时,其它进程不能查询。
时间点 事务A 事务B 1 开启事务 2 修改数据 不可开启事务 3 提交数据 不可开启事务 4 开启事务 5 不可开启事务 修改数据 6 不可开启事务 提交数据 -
使用乐观锁(添加version或时间戳): 在写写并行发生冲突时报异常1,交给程序员处理。
2-事务的隔离级别
事务的四种隔离级别:
种类 | 处理的问题 | 解释 |
---|---|---|
串行化 | 三个读问题都能处理 | 顾名思义,此方法下,对同一数据的访问是串行的。没有并发,就不会出现任何并发问题。此级别基本不使用,一是效率底;二是容易出现死锁。 |
可重复读 | 不能处理幻读 | mysql有两种机制能达到此级别的隔离效果。一、加读锁(读读共享) + 加写锁(读写串行):实现简单,但因为读写无法并行,所以效率低;二、读不加锁 + 加写锁 + MVCC(有兴趣的可以自行百度):实现复杂,但读写可以并行2,效率高。 |
读已提交数据 | 只能处理脏读 | 操作:写时加排它锁,读时不加操作。 引用大佬的话(原文找不到了):在mysql的MVCC机制和该隔离级别的共同作用下,每次select的时候数据库会新生成一个版本号,所以每次select的时候读的不是一个副本 而是不同的副本。每次select之间有其他事务更新了,而我们读取数据并提交了,那就出现了不可重复读。所以虽然此级别效率高,但是mysql不能使用。 |
读未提交数据 | … | 即,不做任何处理。 |