事务
事务就是以可控的方式对数据资源进行访问的一组操作。为了保证事务执行前后,数据资源所承载的系统状态始终处于“正确”状态,事务本身持有4个限定属性,原子性(Atomicity
)、一致性(Consistency
)、隔离性(Isolation
)和持久性(Durability
),这也就是常说的事务的ACID属性。
原子性(Atomicity
)
一个事务包含多个操作,这些操作要么全部执行,要么全都不执行。实现事务的原子性,要支持回滚操作,在某个操作失败后,回滚到事务执行之前的状态。
回滚实际上是一个比较高层抽象的概念,大多数DB
在实现事务时,是在事务操作的数据快照上进行(比如MVCC
),并不修改实际的数据,如果有错并不会提交,所以自然的支持回滚。
而在其他支持简单事务的系统中,不会在快照上更新,而直接操作实际数据。可以先预演一遍所有要执行的操作,如果失败则这些操作不会被执行,通过这种方式很简单的实现了原子性。
一致性(Consistency
)
一致性是指事务使得系统从一个正确的一致性的状态转换到另一个正确的一致性状态。
这里我们举个大家都在说的财务系统的例子.
A
要向B
支付100
元,而A
的账户中只有90
元,并且我们给定账户余额这一列的约束是,不能小于0
.那么很明显这条事务执行会失败,因为90-100=-10
,小于我们给定的约束了.
这个例子里,支付之前我们数据库里的数据都是符合约束的,但是如果事务执行成功了,我们的数据库数据就破坏约束了,因此事务不能成功,这里我们说事务提供了一致性的保证.然后我们再看个例子
A
要向B
支付100
元,而A
的账户中只有90
元,我们的账户余额列没有任何约束.但是我们业务上不允许账户余额小于0
.因此支付完成后我们会检查A
的账户余额,发现余额小于0
了,于是我们进行了事务的回滚.
这个例子里,如果事务执行成功,虽然没有破坏数据库的约束,但是破坏了我们应用层的约束.而事务的回滚保证了我们的约束,因此也可以说事务提供了一致性保证(ps:事实上,是我们应用层利用事务回滚保证了我们的约束不被破坏).最后我们再看个例子
A
要向B
支付100
元,而A
的账户中只有90
元,我们的账户余额列没有任何约束.然后支付成功了.
这里,如果按照很多人的理解,事务不是保证一致性么?直观上账户余额为什么能为负呢.但这里事务执行前和执行后,我们的系统没有任何的约束被破坏.一直都是保持正确的状态.
所以,综上.你可以理解一致性就是:应用系统从一个正确的状态到另一个正确的状态.而ACID
就是说事务能够通过AID
来保证这个C
的过程.C是目的,AID
都是手段.
隔离性(Isolation
)
ISOLATION LEVEL | Dirty Read | Non-Repeatable Read | Phantom Read |
---|---|---|---|
READ UNCOMMITED | 可以出现 | 可以出现 | 可以出现 |
READ COMMITED | 不允许出现 | 可以出现 | 可以出现 |
REPEATABLE READ | 不允许出现 | 不允许出现 | 可以出现 |
SERIALIZABLE | 不允许出现 | 不允许出现 | 不允许出现 |
-
Read Uncommitted
该隔离级别的事务会读到其它未提交事务的数据,此现象也称之为脏读。
准备两个终端先创建一个数据库和一张简单的表;
SET @@session.transaction_isolation = 'READ-UNCOMMITED'; create database test; create table test(id int primary key); insert into test(id) values(1);
创建终端一 开启事务把
test
表中id
改为2
SET @@session.transaction_isolation = 'READ-UNCOMMITED'; use test; begin; update test set id = 2 where id = 1; select * from test; -- 你会看到一条ID为2的记录
创建终端二 开启事务去查看
test
表中的记录SET @@session.transaction_isolation = 'READ-UNCOMMITED'; use test; begin; select * from test -- 这时你会看到终端一中未提交的事务
在终端二中读取到了终端一中未提交的事务,就是产生了脏读大部分业务场景中都不允许脏读的出现,但是此隔离级别下数据库的并发是最好的。
-
Read Committed
一个事务可以读取另一个已提交的事务,多次读取会造成不一样的结果,此现象称为不可重复读问题,
Oracle
和SQL Server
的默认隔离级别。还是拿上面个
test
表连接两个终端,终端一开启事务修改test
表id
字段为3
SET @@session.transaction_isolation = 'READ-COMMITTED'; begin; update test set id = 3 where id =2; select * from test; -- 此时你会看到id=3
启动终端二开启事务,查看
test
表中的数据SET @@session.transaction_isolation = 'READ-COMMITTED'; begin; select * from test; -- 此时你会看到id=2
继续操作终端一
commit; -- 提交终端一的事务
继续操作终端二
select * from test; -- 你会看到这时id=3
终端二在开启一个事务之后,在第一次读取
test
表时(终端一的事务还未提交时)ID=2
,在第二次读取test
表(终端一的事务已经提交时)ID = 3
说明在此隔离隔离级别下可以读取到已提交的事务。 -
Repeatable Read
该隔离级别是
MySQL
默认的隔离级别,在同一个事务里,select
的结果是事务开始时时间点的状态,因此,同样的select
操作读到的结果会是一致的,但是,会有幻读现象。MySQL
的InnoDB
引擎可以通过next-key locks
机制来避免幻读。首先准备一个
test
数据库和一张简单test
表两个连接数据库的终端create datebase test; create table test(id int primary key,name varchar(20)); use test;
终端一开启一个事务
SET @@session.transaction_isolation = 'REPEATABLE-READ'; begin; select * from test; -- test表里什么都没有
终端二开启一个事务
SET @@session.transaction_isolation = 'REPEATABLE-READ'; begin; select * from test; -- test表里什么都没有
操作终端一执行操作事务并提交
insert into test(id,name) values(a,'one'); commit;
操作终端二查询
test
表select * from test; -- 你会发现什么都没有终端二这时是看不见终端一已经提交的的事务的
由于终端二是看不见终端一已经提交的事务那么操作终端二继续执行
id=1
的insert
操作insert into test(id,name) values(1,'two'); ERROR 1062 (23000): Duplicate entry '1' for key 'test.PRIMARY'
我去用终端二去查询时发现表里明明没数据啊那为什么给我报主键冲突的报错呢!这就是该隔离级别下会产生的问题幻读。
-
Serializable
在该隔离级别下事务都是串行顺序执行的,
MySQL
数据库的InnoDB
引擎会给读操作隐式加一把读共享锁,从而避免了脏读、不可重读复读和幻读问题。首先准备一个简单的库和简单的一张表和两个连接终端
create database test; use test; create table test(id int primary key);
操作终端一开启事务向
test
表写入一条记录SET @@session.transaction_isolation = 'SERIALIZABLE'; use test; begin insert into test(id) values(1); -- 不做提交
操作终端二开启事务查询
test
表SET @@session.transaction_isolation = 'SERIALIZABLE'; use test; begin; select * from test; -- 它会一直卡住直到终端一的事务提交
操作终端一提交事务
commit;
一旦事务提交终端二会立马返回
ID=1
的记录,否则会一直卡住,直到超时其中超时参数是由innodb_lock_wait_timeout
控制由于每条select
语句都会加锁,所以该隔离级别并发能力最弱,但是该结论不一定。
持久性(Durability
)
事务一旦提交,则其结果就是永久性的。即使发生宕机的故障,数据库也能将数据恢复,也就是说事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。这只是从事务本身的角度来保证,排除 RDBMS
(关系型数据库管理系统,例如 Oracle、MySQL
等)本身发生的故障。
注:MySQL 使用 redo log
来保证事务的持久性。