由于Mongodb不支持事务(老版本),自己在工作中也从来没有用过事务,今天在面试的时候提到类似这么一个问题:在数据库中要新增一条A记录,同时要修改B记录的一个字段,这个肯定是用事务实现,但是Mongodb不支持事务,你会怎么设计数据库的事务?
很尴尬,我不知道如何回答。
1.事务的用法
先来一段事务的使用方法(网上看到的,不保证正确性)
try {
conn.setAutoCommit(false); //将自动提交设置为false
ps.executeUpdate("修改SQL"); //执行修改操作
ps.executeQuery("查询SQL"); //执行查询操作
conn.commit(); //当两个操作成功后手动提交
} catch (Exception e) {
conn.rollback(); //一旦其中一个操作出错都将回滚,使两个操作都不成功
e.printStackTrace();
}
由以上代码可以看出,将两个操作当作一个事务来执行,执行完毕后可以commit该事务,否则回滚该事务,不会对数据库中的数据产生任何影响。
2.事务的实现
事务有四大特性:ACID
以转账操作为例,A账户转账100元给B账户。
原子性:即两个操作要么都执行,要么都不产生效果,绝对不会产生A-B的中间状态。
一致性:即A账户减少了100元钱,B账户就一定增加了100元,否则就违背了一致性。
隔离性:两个事务之间的操作不应该互相影响。
持久性:数据库事务一旦提交,就不会再被修改。
问题一:原子性和一致性有什么区别?
从描述上来看,原子性保证所有操作必须执行或者都不产生效果,那么只有AB两个状态,即一致性的两种状态。
那为什么说原子性不能保证一致性呢?
因为事务1在执行的时候,事务2有可能会影响到事务1的中间状态,这就引入了隔离性。
问题二:怎么保证原子性?
先写日志,再改数据。
将所有对数据库的操作,都先通过记录日志的方式保存下来,当发生异常情况时,可以重放日志操作来撤销对数据库的修改。
日志记录完整,但再写数据库时异常,重启后这部分数据重放进入数据库。
日志记录异常,日志都不完整,重放时没有commit标记,不会进入数据库。
问题三:怎么保证一致性?
引入隔离性。
问题四:怎么保证隔离性?
各种锁。
悲观锁:
在一个事务未提交前,禁止另一个事务做修改,这样的话当然保证了独立性,但是性能会差。为了提高性能,引入了各种粒度的锁(数据库表级、行级、库级锁,各种性质的锁:排它锁,死锁问题:两阶段锁
乐观锁:
在比较晚提交的事务当中检测数据是否被修改,如果被修改了,就放弃本次提交。
引用知乎--我练功发自内心
区别数据库、操作系统的原子性概念
作者:我练功发自真心
首先说对原子性的理解。数据库的理解和操作系统的理解是截然相反的。数据库的原子性说的其实不是楼上说的那些。数据库的原子性说的是failure atomicity:一个事物要么运行成功,要么完全没有运行。也正如题主所问的(非常贴切!),这东西和一致性直接相关。因为如果一个事务的修改到一半终止掉,那么是不可能保持一致性的!
那么楼上说的是啥呢?楼上说的其实是操作系统对atomic的理解:并发控制。OK为啥这么说,因为操作系统直到现在都没有一个统一的并发控制体系,最常用的并发控制就是:锁。而且,锁在操作系统里是没法和数据进行对应的。所以,在操作系统里,用锁来控制运行的顺序:任何要看到非atomic的操作或者非consistent的线程和Interrupt handler都被锁锁在外面。所以,这是操作系统理解的一致性和原子性。
那么数据库说的一致性和原子性是怎么实现的呢?
一般来说用journaling。
任何的修改都先写在一个journal/log里。当事务提交时,会再写一个叫做commit record的东西。那么想象如下情况:如果断电发生,没有提交的事务完成了一半,这个事务就不会有commit record,那么在恢复的时候就会跳过这个事务,就当它没发生;如果提交的事务完成了,那么你一定会看到一个commit record,这时候你就可以根据journal里的数据来恢复你数据库的内容了,这时候,这个事务完整提交。
还有一个叫做copy on write/shadow paging的方法,一般不在数据库中用。因为数据库往往希望对连续读做优化。(但谁知道以后呢…)
PS 数据库的并发控制非常统一(isolation),楼上说了用锁做控制是比较传统的做法(2PL)。除此之外,还有著名的MVCC,比较激进的OCC方法等等…
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。