mysql事务
事务
什么是事务?
数据库中的事务是指对数据库执行一批操作,这些操作最终要么全部执行成功,要么全部失败,不会存在部分成功的情况。
概括的来说:事务是由一条sql语句或者多条sql语句组成执行单元,这个执行单元是不可分割的,sql语句要么全部执行成功要么全部不执行;
例子:
如果A用户给B用户转账100元,过程如下:
- 从A账户扣100元
- 给B账户加100元
如果没有事务支持,由于某些意外可能会出现:A扣了100,但是B没有增加100,
这对任何人来说都是不可接受的错误
但是,在事务的支持下,只会出现两种结果:
1.操作成功,A账户少了100,B账户多了100
2.操作失败,A、B账户都没有变化
事务的特性(ACID)
原子性(Atomicity)
事务的整个过程如原子操作一样,最终要么全部成功,或者全部失败,这个原子性是从最终结果来看的,从最终结果来看这个过程是不可分割的。
一致性(Consistency)
在事务开始之前和事务结束以后,数据的约束是不会被破坏;
事务执行的前后都是合法的数据状态。数据库的完整性约束包括但不限于:实体完整性(如行的主键存在且唯一)、列完整性(如字段的类型、大小、长度要符合要求)、外键约束、用户自定义完整性(如转账前后,两个账户余额的和应该不变)。
隔离性(Isolation)
一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
隔离性是指,事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
也就是说,并发事务之间的数据是相互隔离的
持久性(Durability)
持久性是指事务一旦提交,它对数据库的改变就应该是永久性的。
也就是说当事务提交之后,数据会持久化存储,修改是永久性的。接下来的其他操作或故障不应该对其有任何影响。
ACID实现原理
事务的隔离性由多版本控制机制和锁实现,而原子性、一致性和持久性通过InnoDB的redo log、undo log和ForceLog at Commit机制来实现。
参考:mysql事务原理
事务的二阶段提交
参考
阶段1:写redo log,事务处于prepare
阶段2:写binlog,事务处于commit
写binlog成功有xid事件,会将xid写入redo log
redo log 与 binlog区别:
①redo log是innodb存储引擎独有的,binlog是不区分存储引擎
②记录内容不同,redo log是物理逻辑日志,记录页的变化过程;binlog是逻辑日志,记录事务具体操作的内容
③写入时间不同,先写入redo log,再写入binlog
④redo log是循环使用文件,binlog每次新增一个文件
mysql中的事务操作
事务一般分为两种:隐式事务和显示事务
在Mysql中,事务默认是自动提交的,所以说每个DML语句实际上就是一次事务的过程。
隐式事务:没有开启和结束的标志,默认执行完SQL语句就自动提交,比如我们经常使用的INSERT、UPDATE、DELETE语句就属于隐式事务。
显示事务:需要显示的开启关闭,然后执行一系列操作,最后如果全部操作都成功执行,则提交事务释放连接,如果操作有异常,则回滚事务中的所有操作。
是否开启隐式事务是由变量 autocommit 控制的
从事务理论的角度来说,可以把事务分为以下几种类型:
- 扁平事务(Flat Transactions)
- 带有保存点的扁平事务(Flat Transaction with Savepoints)
- 链事务(Chained Transactions)
- 嵌套事务(Nested Transaction)
- 分布式事务(Distributed Transactions)
隐式事务
事务自动开启、提交或回滚,比如insert、update、delete语句,事务的开启、提交或回滚由mysql内部自动控制的。
查看变量 autocommit 是否开启了自动提交(默认是on)
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.37 sec)
autocommit
为ON表示开启了自动提交。
显式事务
事务需要手动开启、提交或回滚,由开发者自己控制。
两种方式手动控制事务
方式1:
设置不自动提交事务
set autocommit=0;
执行事务操作
commit|rollback;
默认情况下 autocommit = 1,是自动提交事务的。autommit 是 session 级别的,就是当前连接更改了 autocommit,对其他连接没有影响。设置 autocommit 之后,本次连接的所有 sql 都是事务的形式,比如每次 commit 提交。
也就是说更改变量这种方式只应用于当前连接
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)
mysql> SET autocommit = 0;
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | OFF |
+---------------+-------+
1 row in set (0.00 sec)
示例1:提交事务操作
mysql> create table t1(a int);
Query OK, 0 rows affected (0.01 sec)
mysql> select * from t1;
Empty set (0.00 sec)
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t1 values(1);
Query OK, 1 row affected (0.00 sec)
-- 在提交之后,数据才会被持久化存储
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t1;
+------+
| a |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
示例2:回滚事务操作
mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t1 values(2);
Query OK, 1 row affected (0.00 sec)
mysql> select * from t1;
+------+
| a |
+------+
| 1 |
| 2 |
+------+
2 rows in set (0.00 sec)
-- 数据回滚
mysql> rollback;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t1;
+------+
| a |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
还原autocommit
-- 断开连接或是恢复变量
mysql> set autocommit=1;
Query OK, 0 rows affected (0.00 sec)
方式2:
开启事务的方式 start transaction 和 begin 是相同的
语法:
#开启事务
start transaction;
#执行事务操作
commit|rollback;
示例1:提交事务操作
mysql> select * from t1;
+------+
| a |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t1 values(2);
Query OK, 1 row affected (0.00 sec)
mysql> insert into t1 values (3);
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t1;
+------+
| a |
+------+
| 1 |
| 2 |
| 3 |
+------+
3 rows in set (0.00 sec)
成功插入两条数据
示例2:回滚事务操作
mysql> select * from t1;
+------+
| a |
+------+
| 1 |
| 2 |
| 3 |
+------+
3 rows in set (0.00 sec)
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> delete from t1;
Query OK, 3 rows affected (0.00 sec)
mysql> select * from t1;
Empty set (0.00 sec)
mysql> rollback;
Query OK, 0 rows affected (0.01 sec)
mysql> select * from t1;
+------+
| a |
+------+
| 1 |
| 2 |
| 3 |
+------+
3 rows in set (0.00 sec)
上面例子删除了t1表的数据,然后回滚了事务
同时验证了:
delete语句删除操作作为事务记录在日志中保存,以便进行进行回滚操作。
savepoint保存点
在事务中我们执行了一大批操作,可能我们只想回滚部分数据,怎么做呢?
我们可以将一大批操作分为几个部分,然后指定回滚某个部分。可以使用 savepoin 来实现,
语法:
savepoint 结点名; # 设置保存点,并和rollback结合使用,实现回滚到指定保存点
rollback to 结点名; # 回滚到指定点
示例:
先清空t1表数据
mysql> delete from t1;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t1;
Empty set (0.00 sec)
savepoint演示:
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t1 values(1);
Query OK, 1 row affected (0.00 sec)
mysql> savepoint part1; #设置保存点part1
Query OK, 0 rows affected (0.00 sec)
mysql> insert into t1 values(2);
Query OK, 1 row affected (0.00 sec)
mysql>
mysql> select * from t1;
+------+
| a |
+------+
| 1 |
| 2 |
+------+
2 rows in set (0.00 sec)
mysql> rollback to part1; #回滚到保存点part1
Query OK, 0 rows affected (0.00 sec)
mysql> commit; #提交事务
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t1;
+------+
| a |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
savepoint 节点名
需要结合 rollback to 节点名
一起使用,可以将保存点到 rollback to 之间的操作回滚掉。
类似于恢复虚拟机快照
只读事务
应用场合:
如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性;
如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持。
一次执行多次查询来统计某些信息,这时为了保证数据整体的一致性,要用到只读事务
语法:
start transaction read only;
示例:
mysql> start transaction read only;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t1;
+------+
| a |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
mysql> delete from t1;
ERROR 1792 (25006): Cannot execute statement in a READ ONLY transaction.
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t1;
+------+
| a |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
事务的一些问题
脏读
脏读(Dirty Reads):一个事务正在对一条记录做修改,在这个事务完成并提交前, 这条记录的数据就处于不一致状态; 这时, 另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做"脏读"。
更新丢失
更新丢失(Lost Update):当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题 --最后的更新覆盖了由其他事务所做的更新。例如,两个编辑人员制作了同一 文档的电子副本。每个编辑人员独立地更改其副本,然后保存更改后的副本,这样就覆盖了原始文档。 最后保存其更改副本的编辑人员覆盖另一个编辑人员所做的更改。如果在一个编辑人员完成并提交事务之前,另一个编辑人员不能访问同 一文件,则可避免此问题。
读已提交
从字面上我们就可以理解,即一个事务操作过程中可以读取到其他事务已经提交的数据。
事务中的每次读取操作,读取到的都是数据库中其他事务已提交的最新的数据(相当于当前读)
可重复读
一个事务操作中对于一个读取操作不管多少次,读取到的结果都是一样的。在事务期间,及时表已经变更,事务读取的结果不会改变。
不可重复读
不可重复读(Non-Repeatable Reads):一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读”
幻读
幻读 (Phantom Reads): 一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”
幻读现象例子:
可重复读模式下,比如有个用户表,手机号码为主键,有两个事物进行如下操作
事务A操作如下: 1、打开事务 2、查询号码为X的记录,不存在 3、插入号码为X的数据,插入报错(为什么会报错,先向下看) 4、查询号码为X的记录,发现还是不存在(由于是可重复读,所以读取记录X还是不存在的)
事物B操作:在事务A第2步操作时插入了一条X的记录,所以会导致A中第3步插入报错(违反了唯一约束)
上面操作对A来说就像发生了幻觉一样,明明查询X(A中第二步、第四步)不存在,但却无法插入成功
幻读可以这么理解:事务中后面的操作(插入号码X)需要上面的读取操作(查询号码X的记录)提供支持,但读取操作却不能支持下面的操作时产生的错误,就像发生了幻觉一样。
幻读和不可重复读的区别:
- 不可重复读的重点是修改:在同一事务中,同样的条件,第一次读的数据和第二次读的数据不一样。(因为中间有其他事务提交了修改)
- 幻读的重点在于新增或者删除:在同一事务中,同样的条件,,第一次和第二次读出来的记录数不一样。(因为中间有其他事务提交了插入/删除)
事务隔离级别
当多个事务并发进行的时候,如何确保当前事务中数据的正确性,比如A、B两个事物同时进行的时候,A是否可以看到B已提交的数据或者B未提交的数据,这个需要依靠事务的隔离级别来保证,不同的隔离级别中所产生的效果是不一样的。
事务隔离级别主要是解决了上面多个事务之间数据可见性及数据正确性的问题。
隔离级别分为4种:
1. 读未提交:READ-UNCOMMITTED
2. 读已提交:READ-COMMITTED
3. 可重复读:REPEATABLE-READ
4. 串行:SERIALIZABLE
上面4中隔离级别越来越强,会导致数据库的并发性也越来越低。
查看隔离级别
mysql> show variables like 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name | Value |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set (0.00 sec)
mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ |
+-------------------------+
1 row in set (0.00 sec)
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)
mysql> show variables like '%isolation';
+-----------------------+-----------------+
| Variable_name | Value |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
| tx_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
2 rows in set (0.00 sec)
#可重复读
这里有两个变量transaction_isolation
和tx_isolation
,貌似作用相似
官网查询后发现:
从5.7.20版本开始,transaction_isolation作为tx_isolation的别名被引入,而tx_isolation在8.0版本后被废弃了,应用应该使用transaction_isolation,而非tx_isolation。
所以尽量使用transaction_isolation
隔离级别的设置
步骤:修改配置文件,重启数据库
修改mysql中的my.init文件,我们将隔离级别设置为:READ-UNCOMMITTED,如下:
/*隔离级别设置:
READ-UNCOMMITTED读未提交,
READ-COMMITTED读已提交,
REPEATABLE-READ可重复读,
SERIALIZABLE串行
*/
-- 配置文件中添加
transaction-isolation=READ-UNCOMMITTED
-- 然后重启数据库
systemctl restart mysqld
mysql> show variables like 'transaction_isolation';
'+-----------------------+------------------+
| Variable_name | Value |
+-----------------------+------------------+
| transaction_isolation | READ-UNCOMMITTED |
+-----------------------+------------------+
1 row in set (0.00 sec)
各种隔离级别中会出现的问题
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ-UNCOMMITTED | 有 | 有 | 无 |
READ-COMMITTED | 无 | 有 | 无 |
REPEATABLE-READ | 无 | 无 | 有 |
SERIALIZABLE | 无 | 无 | 无 |
幻读只会在可重复读级别中才会出现,其他级别下不存在。
脏读:一个事务可以读取另一个并行的事务的脏页(未提交的update,delete,insert)
不可重复读:一个事务可以读取另一个并行的事务的已提交的修改操作update
幻读:一个事务可以读取另一个并行的事务的已提交的插入操作insert
读未提交:(READ-UNCOMMITTED)
产生脏读
查看当前隔离级别:
mysql> show variables like 'transaction_isolation';
+-----------------------+------------------+
| Variable_name | Value |
+-----------------------+------------------+
| transaction_isolation | READ-UNCOMMITTED |
+-----------------------+------------------+
1 row in set (0.00 sec)
示例;
-- 先清空t1表
delete from t1;
select * from t1;
按时间顺序在2个窗口中执行下面操作:
时间点 | 窗口A | 窗口B |
---|---|---|
T1 | start transaction; | |
T2 | select * from t1; | |
T3 | start transaction; | |
T4 | insert into t1 values (1); | |
T5 | select * from t1; | |
T6 | select * from t1; | |
T7 | commit; | |
T8 | commit; |
效果:
窗口A执行完T2时,没有查询到数据,在T6时,窗口B已经执行完了插入,B能看到数据,
但在T6时间点,窗口A能查询到B插入的数据,说明产生了脏读
窗口A在T2、T6两次查询,结果不一致,说明不可重复读
结论:在读未提交情况下,可以读取到其他事物未提交的数据,多次读取结果不一样,出现了脏读、不可重复读
读已提交(READ-COMMITTED)
不会产生脏读,产生不可重复读;
将隔离级别设置为READ-COMMITTED
/*隔离级别设置:
READ-UNCOMMITTED读未提交,
READ-COMMITTED读已提交,
REPEATABLE-READ可重复读,
SERIALIZABLE串行
*/
-- 配置文件改为
transaction-isolation=READ-COMMITTED
-- 重启服务,查看隔离界别
mysql> show variables like 'transaction_isolation';
+-----------------------+----------------+
| Variable_name | Value |
+-----------------------+----------------+
| transaction_isolation | READ-COMMITTED |
+-----------------------+----------------+
1 row in set (0.00 sec)
-- 清空t1表
delete from t1;
select * from t1;
按时间顺序在2个窗口中执行下面操作:
时间点 | 窗口A | 窗口B |
---|---|---|
T1 | start transaction; | |
T2 | select * from t1; | |
T3 | start transaction; | |
T4 | insert into t1 values (1); | |
T5 | select * from t1; | |
T6 | select * from t1; | |
T7 | commit; | |
T8 | select * from t1; | |
T9 | commit; |
T5时间点B有数据,T6时间点无数据,A看不到B的数据,说明没有产生脏读
T6时间点A无数据,在T8时,B已经提交了事务,A能看到B的数据,说明可以读取到已提交的事务
T2、T6的A无数据,T8的A有数据,多次读取的结果不一致,说明不可重复读
结论:读已提交情况下,无法读取到其他事物还未提交的事务,可以读取到其他事物已提交的数据。多次读取结果不一致,没有出现脏读,出现了读已提交,不可重复读
可重复读(REPEATABLE-READ)
不会产生脏读,不会产生不可重复读;会产生幻读;
将隔离级别设置为REPEATABLE-READ
/*隔离级别设置:
READ-UNCOMMITTED读未提交,
READ-COMMITTED读已提交,
REPEATABLE-READ可重复读,
SERIALIZABLE串行
*/
-- 配置文件改为
transaction-isolation=REPEATABLE-READ
-- 重启服务,查看隔离界别
mysql> show variables like 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name | Value |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set (0.01 sec)
-- 清空t1表
delete from t1;
select * from t1;
时间点 | 窗口A | 窗口B |
---|---|---|
T1 | start transaction; | |
T2 | select * from t1; | |
T3 | start transaction; | |
T4 | insert into t1 values (1); | |
T5 | select * from t1; | |
T6 | select * from t1; | |
T7 | commit; | |
T8 | select * from t1; | |
T9 | commit; | |
T10 | select * from t1 |
T2-A、T6-A窗口:无数据,T5-B:有数据,A看不到B的数据,说明没有脏读。
T8-A:无数据,此时B已经提交了,A看不到B已提交的数据,A中3次读的结果一样都是没有数据的,说明可重复读。
结论:可重复读情况下,未出现脏读,未读取到其他事务已提交的数据,多次读取结果一致,即可重复读。
幻读演示
幻读只会在 REPEATABLE-READ (可重复读)级别下出现,需要先把隔离级别改为可重复读。将隔离级别置为 REPEATABLE-READ
上面例子已经是在可重复读的隔离级别,这里不用做更改
mysql> show variables like 'transaction_iso%';
+-----------------------+-----------------+
| Variable_name | Value |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set (0.00 sec)
准备测试数据:
mysql> create table t_user(id int primary key,name varchar(16) unique key);
Query OK, 0 rows affected (0.01 sec)
mysql> insert into t_user values (1,'zhangsan'),(2,'zhangsan');
ERROR 1062 (23000): Duplicate entry 'zhangsan' for key 'name'
mysql>
mysql> select * from t_user;
Empty set (0.00 sec)
创建了t_user表,id为主键,name设置唯一键,不能重复
按时间顺序在2个窗口中执行下面操作:
时间 | 窗口A | 窗口B |
---|---|---|
T1 | start transaction; | |
T2 | start transaction; | |
T3 | insert into t_user values (1,'zhangsan'); | |
T4 | select * from t_user where name = 'zhangsan';(有数据) | |
T5 | select * from t_user where name = 'zhangsan';(没有数据) | |
T6 | commit; | |
T7 | insert into t_user values (2,'zhangsan');(报错:不能重复) | |
T8 | select * from t_user where name = 'zhangsan';(没有数据) | |
T9 | commit; |
T8插入时会报错:
mysql> insert into t_user values(2,'zhangsan');
ERROR 1062 (23000): Duplicate entry 'zhangsan' for key 'name'
分析:
A在开始事务后查询zhangsan用户是否存在,发现没有数据,不存在,于是在T7执行插入,
但报错,数据已经存在,因为T6时间点B事务已经插入了zhangsan用户这条数据。
于是T8时刻再次查询,发现依然没有这条数据,确实不存在
对于A来说,就想幻觉一般,因此叫幻读
总结:幻读的重点在于新增或者删除:在同一事务中,同样的条件,,第一次和第二次读出来的记录数不一样。(因为中间有其他事务提交了插入/删除)
串行(SERIALIZABLE)
读加锁,写加锁,读写冲突
不会产生脏读,不会产生不可重复读;不会产生幻读;
SERIALIZABLE会让并发的事务串行执行(多个事务之间读写、写读、写写会产生互斥,效果就是串行执行,多个事务之间的读读不会产生互斥)
读写互斥:事务A中先读取操作,事务B发起写入操作,事务A中的读取会导致事务B中的写入处于等待状态,直到A事务完成为止。
表示我开启一个事务,为了保证事务中不会出现上面说的问题(脏读、不可重复读、读已提交、幻读),那么我读取的时候,其他事务有修改数据的操作需要排队等待,等待我读取完成之后,他们才可以继续。
写读、写写也是互斥的,读写互斥类似
示例:
#将隔离级别设置为SERIALIZABLE
transaction-isolation=SERIALIZABLE
#重启mysql,查看隔离级别
mysql> show variables like 'transaction_isolation';
+-----------------------+--------------+
| Variable_name | Value |
+-----------------------+--------------+
| transaction_isolation | SERIALIZABLE |
+-----------------------+--------------+
1 row in set (0.01 sec)
清空t1表数据:
delete from test1;
select * from test1;
按时间顺序在2个窗口中执行下面操作:
时间 | 窗口A | |
---|---|---|
T1 | start transaction; | |
T2 | select * from t1; | |
T3 | start transaction; | |
T4 | insert into t1 values(1);(执行后卡主了,表示被阻塞,读写互斥) | |
T5 | commit; | |
T6 | commit; |
T4执行时卡主,短时间后报错,或者T5-A执行完后,T4-B才执行完毕:
mysql> insert into t1 values(1);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
按时间顺序运行上面的命令,会发现T4-B这样会被阻塞,直到T5-A执行完毕。
上面这个演示的是读写互斥产生的效果,还有写读、写写互斥的效果。但归根结底是在每个读的数据行上加上共享锁。
可以看出来,事务只能串行执行了。串行情况下不存在脏读、不可重复读、幻读的问题了。
并发事务处理带来的问题的解决办法:
-
“更新丢失”通常是应该完全避免的。但防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新的数据加必要的锁来解决,因此,防止更新丢失应该是应用的责任。
-
“脏读” 、 “不可重复读”和“幻读” ,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决:
-
- 一种是加锁:在读取数据前,对其加锁,阻止其他事务对数据进行修改。
- 另一种是数据多版本并发控制(MultiVersion Concurrency Control,简称 MVCC 或 MCC),也称为多版本数据库:不用加任何锁, 通过一定机制生成一个数据请求时间点的一致性数据快照 (Snapshot), 并用这个快照来提供一定级别 (语句级或事务级) 的一致性读取。从用户的角度来看,好象是数据库可以提供同一数据的多个版本。
SQL标准定义了4类隔离级别,每一种级别都规定了一个事务中所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。
关于隔离级别的选择
- 隔离级别越高,并发性也低,比如最高级别 SERIALIZABLE 会让事物串行执行,并发操作变成串行了,会导致系统性能直接降低
- 具体选择哪种需要结合具体的业务需求特性来选择。
- Mysql默认的事务隔离级别是可重复读(Repeatable Read)
- 读已提交(READ-COMMITTED)通常用的比较多。