一直以来,都认为在开启事务下如果往DB里面进行操作,过程中没有发生异常,commit一定会成功。由此可以推出一个矛盾的结论,如果有一个connect开启事务,增加一条记录,假如这个记录的primary key为name,输入的记录name为joe,未提交,另外一个connect开启事务,增加同一的记录,这个增加的结果会怎么样呢?假如增加成功,则第一个connect开启的事务commit应该会失败,如果不失败则会增加2条具有相同key的记录,违背了数据库的唯一性约束。具体情况是怎么样的呢?
有句话说得好,理论解决不了就实践,实践解决不了再理论,上面的推导肯定有错误的或者有些细节的地方没考虑到。ok,让我先实践吧,实践出真知。</p><p>我首先用toad工具向oracle一张表插入一条记录,但不commit,然后用java程序开启另一个connect往同一张表写相同记录,commit,run代码,一直被block。奇怪难得哪里有死锁,或未唤醒的地方。测试程序都非常简单。先贴出代码,免得大家说我瞎扯。
public class DBTest {
public static void main(String[] args) {
String sDBDriver = "com.inet.ora.OraDriver";
String sConnStr = "jdbc:inetora:10.224.188.10:1522:shcnc2?streamstolob=true";
Connection conn = null;
Statement stmt = null;
try {
Class.forName(sDBDriver);
conn = DriverManager.getConnection(sConnStr, "dms2", "pass");
stmt = conn.createStatement();
// stmt.executeQuery("create table aaa(aaa int)");
stmt.executeUpdate("insert into joetesttb values('abce')");
} catch (Exception e) {
e.printStackTrace();
}
}
}
因此我们总结一下,如果connect相关事务涉及到的记录已经被上锁(即使看不到该记录),将阻塞sql语句的执行,直到记录解锁为止。似乎问题到此为止了,但遇到这样的问题如果不再深入一下,就太浪费了。
事实上我们还不能确定是不是真的是记录上锁了,为了验证这个问题,让我们稍稍修改前面的代码,插入另外一个值,跟另一个未提交事务的值不一样,执行,不再阻塞了。果然和我们猜测一样,确实记录上锁。
让我们把问题再引申一下:既然可以通过这种方式来阻塞跨进程的线程,是否可用提供分布式锁的功能呢?我们知道,在DMS中也用了DB锁,但那种锁的实现是通过轮询,如果db中存在该记录,则挂起当前线程,通过间隔一段时间再尝试,这种锁在遇到高并发高冲突的情况下非常糟糕,锁的敏感度不高。线程可能长期到不到锁。采用DB record锁应该是相对比较好的模式,当然采用DB记录锁来做为分布式锁在salebility方面有很大的约束。如果需要具有高可靠可伸缩的分布式式锁服务,使用我们基于zookeeper实现的分布式锁应该是最佳方案。
突然使我想到在DMS程序调试过程中遇到过一直被阻塞,但通过监控线程没有发现死等或者死锁的情况,始终想不通是什么问题,很有可能是DB锁导致的。
全文总结,啰啰嗦嗦半天来个最终的总结,很多小小的问题,如果我们逐步深入都有很多值得玩味的地方,对于我们程序员来说,需要善于思考推理和实践。