浏览 2103 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2012-12-25
今天不知不觉想到数据库的乐观锁和悲观锁,遂想写个程序测测,却发现了另一个问题,Mysql InnoDB的Read-Repeatable事务级别使用不当会存在数据一致性问题。
如下的测试程序:
public class OptimisticAndPessimisticLockTest2 { public static void main(String[] args) throws Exception { //创建测试表和数据 initDatabase(); //创建两个线程同时操作同一条记录 OptimisticThread ot1 = new OptimisticThread(newConnection(), "O1"); OptimisticThread ot2 = new OptimisticThread(newConnection(), "O2"); ot1.start(); ot2.start(); } public static Connection newConnection() throws Exception { Class.forName("com.mysql.jdbc.Driver"); Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root"); return con; } public static void initDatabase() throws Exception { Connection con = newConnection(); Statement stmt = con.createStatement(); stmt.execute("drop table if exists locktest"); stmt.execute("create table locktest( name varchar(10)) ENGINE=InnoDB"); stmt.executeUpdate("insert into locktest values('XJD')"); con.close(); } public static class OptimisticThread extends Thread { Connection con; String name; public OptimisticThread(Connection con, String name) { this.con = con; this.name = name; } @Override public void run() { try { //设置事务级别为可重复读 this.con.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ); this.con.setAutoCommit(false); //开始事务 System.out.println("Started Transaction...: " + name); Statement stmt = this.con.createStatement(); //先查询一下是否有'XJD'的记录 ResultSet rs = stmt.executeQuery("select * from locktest where name='XJD'"); if (rs.next()) { System.out.println("Got Record: " + name + " value: " + rs.getString(1)); } rs.close(); Thread.sleep(5000);//暂停5s让另一个线程也查询完成 //更新'XJD'的记录 int i = stmt.executeUpdate("update locktest set name = '" + name + "' where name = 'XJD'"); System.out.println("Update Record: " + name + " count: " + i);//更新成功后i为1 Thread.sleep(5000);//暂停5s让另一个线程也作更新操作 //查询更新后的记录 rs = stmt.executeQuery("select * from locktest where name='" + name + "'"); if (rs.next()) { System.out.println("Got Record: " + name + " value: " + rs.getString(1)); } rs.close(); //查询原来的记录 rs = stmt.executeQuery("select * from locktest where name='XJD'"); if (rs.next()) { System.out.println("Got Record: " + name + " value: " + rs.getString(1)); } rs.close(); Thread.sleep(10000); System.out.println("Commiting Transaction...: " + name); this.con.commit(); this.con.close(); } catch (SQLException e) { System.out.println("Exception in " + name + ": " + e); e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
输出结果:
Started Transaction...: O1 Got Record: O1 value: XJD Started Transaction...: O2 Got Record: O2 value: XJD Update Record: O1 count: 1 Got Record: O1 value: O1 Commiting Transaction...: O1 Update Record: O2 count: 0 //见下一行的说明 Got Record: O2 value: XJD //此处在O2线程中还可以查询到XJD记录,但前一条的Update结果却是0 Commiting Transaction...: O2 从输出结果中注释的两行可以看出,Read-Repeatable事务级别容易出现问题: O1和O2两个线程都开启了事务--O1和O2中都可以查询到XJD的记录--O1更新并提交了XJD记录为'O1'--O2中Update语句返回0可知O2没有更新到‘XJD'的记录--但是O2中仍可使用查询语句查询到‘XJD'的记录(因为可重得读事务设置)--但此时问题就来了,我在O2中可查询到记录,为什么更新不到呢????
从上面的分析,可以看出,Read-Repeatable事务级别容易出现业务上的问题,比如我们在一个事务中查询到一条记录,而后我们对该记录进行操作,发现这些操作跟本不起作用,如果业务比较复杂,跨度大,很容易使我们“迷惑“,我们错在哪,为什么查询到了,却更新不到!!!
难怪Oracle不支持Read-Repeatable事务,在对Oracle执行:
con.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ); 抛出异常:
java.sql.SQLException: 仅 READ_COMMITTED 和 SERIALIZABLE 是有效的事务处理级 我使用的是Mysql默认安装,发现Mysql Innodb默认使用的就是Read-Repeatable事务,所以只能说要谨慎吧!
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2012-12-30
学习了,学习了
|
|
返回顶楼 | |