一、事务的概念
一组数据库操作要么全部成功,要么全部失败,目的是为了保证数据的最终一致性
二、事务的四大特性(ACID)
- 原子性(Actomicity):当前事务的操作要么成功,要么失败,原子性是由undo log日志来保证
- 一致性(Consistency):使用事务的最终目的,由业务代码正确逻辑保证
- 隔离行(Isolation):在事务并发执行时,他们内部的操作不能互相干扰
- 持久性(Durability):一旦提交了事务,它对数据库的改变就应该是永久性的。持久性是由redo log日志来保证
三、事务的隔离级别
InnoDB引擎中提供了四种隔离级别,级别越高事务的隔离行越好,但性能就越低,而隔离性是由mysql的各种锁以及MVCC机制来实现
- READ UNCOMMITTED(读未提交):一个事务可以读到另一个事务未提交的数据。有脏读的可能
- READ COMMITTED(读已提交):事务只能读取到已经提交的数据。有不可重复读的可能性
- REPEATABLE READ(可重复读,MySQL 默认级别):在一个事务中多次读取同一条数据时,结果始终相同。有幻读读可能
- SERIALIZABLE(可串行化):最严的隔离级别,事务完全串行执行。无上述问题
三、脏读、不可重复读、幻读都是什么?
- 脏读:是指一个事务读取了另一个事务未提交的事务。
例:开启事务修改id是1的数据,但不提交事务。再开一个事务查询id是1的数据,但是不提交事务。
set tx_isolation = 'read-uncommitted';
begin;
update test_tab set blance = blance + 500 where id =1;
commit ;
set tx_isolation = 'read-uncommitted';
begin ;
select * from test_tab where id = 1;
commit ;
在事务修改sql事务未提交的情况下,查询结果是更新后的数据。在修改未提交事务时,被另一个事务读去到结果,可见是错误。脏读也就是写的事务未提交时,被另一个事务读区到结果。
- 不可重复读:不可重复读是指只能读去到已经提交了的事务。
例:
还原表中的数据,
set tx_isolation = 'read-committed';
begin;
update test_tab set blance = blance + 500 where id =1;
commit ;
set tx_isolation = 'read-committed';
begin ;
select * from test_tab where id = 1;
commit ;
执行步骤:
1、开启一个事务,在事务中更新数据,不提交事务。再开启一个事务,查询正在更新的那条数据,不提交事务。
2、步骤1更新数据的事务提交,再次查询那条数据,不提交事务。
3、步骤1更新语句再次执行,不提交事务。再次查询那条数据,不提交事务。
4、步骤3的更新事务提交。再次查询那条数据,不提交事务。
5、查询的事务提交。
查询的事务无论是否提交,读到的数据都是已经提交事务的数据。
- 幻读:
还是将数据还原,重新执行下边的sql
set tx_isolation = 'repeatable-read';
begin;
update test_tab set blance = blance + 500 where id =1;
commit ;
set tx_isolation = 'repeatable-read';
begin ;
select * from test_tab where id = 1;
select * from test_tab where id = 1;
commit ;select * from test_tab where id = 1;
步骤:
1、1、开启一个事务,在事务中更新数据,不提交事务。再开启一个事务,查询正在更新的那条数据,不提交事务。
2、2、步骤1更新数据的事务提交,再次查询那条数据,不提交事务。
3、步骤1更新语句再次执行,不提交事务。再次查询那条数据,不提交事务。
4、步骤3的更新事务提交。再次查询那条数据,不提交事务。
5、查询的事务提交,再次查询。
在一个事务里,只要读过了这条数据,后边再读这条数据都以第一次读到的这条数据为主。
那么如何解决这个问题呢?
第一个办法就是用更更高的隔离级别。
第二个办法用乐观锁机制,加上一个version字段,查询的时候将version字段查出来select 业务字段,version from table,更新的时候每次version字段+1 update table set 业务字段, version = version + 1 where version = 前边查出来的version值,知道影响行数大于0。
第三个办法可以使用数据库的悲观锁,如果字段设计到计算的时候,用业务字段 = 业务字段 + 数量,因为mysql的insert、update会有写锁,每次都会用数据库中的最新的数据进行操作