之前的一篇文章中,关于RC 和 RR 隔离级别的问题,在文章尾部建议在大部分场景下,为了高并发和性能的需求,我们都建议使用RC的数据库隔离级别。这里被读者指出,这样的建议是有问题的,基于这个事情,读者也给我一些建议。这里我也进行了一些研究。
这里首先感谢这位读者,同时在翻看了读者的给出的文章链接后,我也想更加深入,之前的确在不少金融机构工作过,也真的未见这些机构的数据库的隔离级别被强制为RR,同时ORACLE的隔离级别也只能使用RC,所以在这样的情况下,到底金融程序中是如何在使用RC数据库隔离级别的时候,还能避免幻读的。
在程序设计中,程序员口中经常会提到,乐观锁和悲观锁的概念,而程序员和DBA 之间,或者说DBA是否能理解程序设计中带有的乐观锁和悲观锁的概念。
在程序中的悲观锁被使用时,他会认为自己在使用数据的时候会有其他的线程来去使用它的数据或修改数据,所以在他使用数据的时候,会对数据进行加锁的工作,也就是他要修改什么什么程序会对这些数据进行加锁的处理,在JAVA程序中的synchronized 的关键字和 Lock的实现都是悲观锁的形式。
与其对应的是程序中的乐观锁,乐观锁认为自己在使用数据的时候,不会有别的线程来修改数据,对于自己线程使用的数据是不会程序进行加锁的处理,只是在更新数据的时候会去判断之前有没有线程更新数据,如果这个数据没有被更新则当前自己的线程修改数据是成功的,如果不是那么就会报重试或异常。
经过上面的信息的描述,程序本身针对自己的要使用的数据是有相关的数据锁的,
比如程序中的乐观锁实现的方式有如下的方式:
版本号控制
CAS 算法
————————
1 版本号控制:版本号控制是程序中控制要修改数据的一种方式,他会在数据表中加入一列,来存储版本号,来表达数据版本。他会在对数据行操作的时候,读取这个值,当在数据提交时会再次读取这个值,如果这个值和事务开启时的值相同,那么就证明这个数据没有被别的事务修改过,则可以进行提交,否则不能提交,根据业务逻辑是事务失败,或者事务重新初始化,进行再次的工作。
2 CAS 算法 compare and swap (比较与交换),CAS 是一种无锁的方案来实现同样的多线程下安全更新数据,这个方案也叫 Non-blocking Synchronization.
这个方案主要是在JDK1.5中添加的 JAVA.util.concurrent(J.U.C), 他在实现的原理上是通过对于要改变的数据线读入到内存中,然后将要修改的值再次读入,如果这两个值一致,说明可以进行更新,则直接将值更新为要更新的值,否则失败。这样的方式相对于 JAVA中的 synchronized 的阻塞方式,有了性能的提升和改进。
具体需要参见 CAS原理。
除了上面说的乐观锁,还有刚才提到的synchronized ,这就属于悲观锁的范畴了悲观锁在实现的机制上有ReentrantLock,这是一种重入锁
代码语言:javascript复制public class Widget {
public synchronized void doSomething() {
System.out.println("方法1执行...");
doOthers();
}
public synchronized void doOthers() {
System.out.println("方法2执行...");
}
}
上面的案例中,使用的就是synchronized ,上面有 doSomething 和 doother 两个调用的方法,doSomething方法在调用数据的时候是独占的,如果使用doother()的方式来调用的话,是必须将被doSomething占中资源被是否才可以被重新调用。所以当数据被doSometing方法调用的时候,此时数据是不能被其他的方法锁调用,除非资源被释放。
说到这里,作者已经明晰了两点
1 数据操作中,程序会对数据的安全性进行兜底,通过乐观锁或悲观锁在JAVA 程序中进行实现。
2 乐观锁和悲观锁,也有倾向性,在高并发的环境中,如果乐观锁被大量使用,则会产生一些热点数据更新失败的场景,但如果使用了悲观锁,则在数据操作中,对于数据使用的效率进行了挑战,或者说数据的更新效率低。
以上为DBA 对于数据更新安全,在程序中或者说JAVA 程序中进行兜底或配合 RC数据隔离级别后如何安全更新数据的知识的一个补充学习。