数据库事务
为一个逻辑单元执行一系列的操作,要么完全执行,要么完全不执行。
事务可以确保非事务性单元内所有操作都可以完成,如果又失败的那么就不会永久的更新数据。
要满足一个事务必须要满足ACID(原子性、一致性、隔离性、持久性)
原子性(Atomic)
事务必须是原子工作单元:对于其数据修改,要没全执行要么都都不执行。通常来说一个事务内的多个操作时有共同目的的,并且时相互依赖。如果只执行其中一个那么就会达不到我们所要的目标。
默认情况下执行一条sql就是一个单独的事务,事务是自动提交的。我们要显示的使用的话,应该使用start transaction 来开启一个事务。
那数据库是怎么做到的呢,许多数据库采用的是日志机制,首先我们在提交事务的时候先把事务写在日志上。如果出现问题从日志上恢复。(我猜的。。。)
一致性 (consistent)
事务在完成时,必须使所有数据都保持一致状态。也就是说一个事务执行之前喝执行之后都应该保持一致性状态。银行转帐的例子:一共1000元,不管转几次总和都应该时1000元。满足完整性的约束条件。
怎么做到的:
- 数据库机制层面
在一个事务执行之前和执行之后,数据会符合你设置的约束(唯一约束、外键约束、Check 约束等)和触发器设置。 - 业务层面
对于业务层面来说就是保持业务等一致性,就比如上面钱的总和要一致。
隔离性(insulation)
在并发操作情况下,事务所做的修改必须要与其他事务隔离开。事务查看数据时的状态要么是个另一个并发事务修改之前的状态,要么时另一个事务修改之后的状态,事务不会查看中间状态的数据。
我们可以想下我们要达到这种效果一般,对于两个并发的事务A1 A2 在事务A1看起来,A2要要么时在A1没开始之前就已经执行结束,要么就是在已经执行之后在开始执行。这样每个事务就不会感觉到其他事务在并发的执行。这种完全隔离就会使得变成了串行执行。
一般情况下我们都会降低隔离级别来换取更大的吞吐量。
持久性(Duration)
事务执行完成后,它对于系统的影响时永久性的。即便时在数据库出现故障的时候也不会丢失事务的操作。
具体数据库是怎么做到的呢,事务中对数据库的修改会在写入数据之前先写入到事务日志中(和原子性差不多),然后事务日志按照顺序排好,当数据库朋克或者是服务器当掉了在重新启动数据库的时候会把事务日志中本应该执行的执行到数据库中。
事务之间的相互影响
由于完全的隔离性是不现实的,所以在数据不能保证隔离性的时候我们数据库会发生什么样的问题:
脏读
脏读是指在一个事务处理过程中读取了另外一个没有提交的事务中的数据。这样会造成很多不准确的问题。一个最典型的是:在一个事务回滚了的情况。比如:
update account set money=money+100 where name="张三"
update account set money=money-100 where name="李四"
在执行完第一条的时候,还没提及的时候,李四通知张三我已经转钱了,张三这时如果能查看到确实已经转过来了,然后就把李四的钱先扣了,但是后面又回滚了,这时候张三的钱并没有增长100。
不可重复读
不可重复读是指在一个事务中我们多次查询一个值却得到了不同的结果。这时由于在一个事务中被另一个事务提交并修改了。
其实在很多情况下不可重复读并不是问题,因为我们在多个查询某个数据的时候默认的是以最后查询到的结果为主。
幻读
幻读是事务非独立执行时发生的一种现象。当一个事务对全表的某一项做修改的操作,如果在这中间的时候,另外一个事务插入了一行新的数据,而这个数据是事务1要修改的值,在事务1执行完成查看的时候会发现我怎么有一个没有更改成功。就产生了幻觉。
比较:
幻读和不可重复读都是读取了一条已经提交完成的事务,而脏读则是读取了一条还没有提交的事务所修改过的数据。幻读和不可重复读懂区别就是不可重复读是针对的一条数据单个的别的事务提交的数据,而幻读则是针对的是一批数据
丢失更新
两个事务同时读取同一条记录,A先修改,B也修改,B并不知道A修改过,则B提交的数据就覆盖了A修改的结果。
隔离级别
针对上面会出现的情况,我们提供了四种隔离级别:
- Serializable (串行化):最高级别,可以避免上面的所有情况。以锁表的方式,使得其他事务职能在锁外等着,效率很低
- Repeatable read (可重复读):可以避免脏读、不可重复读 、丢失更新
- Read commited (已提交读):可以避免脏读
- Read Uncommited(未提交读):啥都避免不了
在mysql中默认的隔离级别是 可重复读,我们可以使用select @@tx_isolation;
来查看当前事务的隔离级别。然后我们使用set [global | session] transaction isolation level 隔离级别名称
如果要是使用的是JDBC,那么每次设置只是在当前session中有效,对其他的session中是无效的。
具体着几种级别是怎么实现和区分的:是使用不同的锁来实现的。
- 未提交读:在读数据时不会检查和使用任何锁。所以可以得到没有提交的数据
- 已提交读:只读取提交读数据,等待其他事务释放排他锁,也就是说当前事务会被其他事务的排他锁所阻塞。读数据时产生的共享锁会在读操作结束完立即释放,不会等到事务结束的时候释放
- 可重复读:保持共享锁一致到事务结束
- 可串性读:工作方式类似于可重复读,但它相当于不是行级锁,是页级锁或者是表锁,会组织查询新插入读所涉及到的范围