zoukankan      html  css  js  c++  java
  • JDBC学习笔记(7)——事务的隔离级别&批量处理

    数据库事务的隔离级别

    对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:
    脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段. 之后, 若 T2 回滚, T1读取的内容就是临时且无效的.
    不可重复读: 对于两个事务 T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段. 之后, T1再次读取同一个字段, 值就不同了.
    幻读: 对于两个事务 T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行. 之后, 如果 T1 再次读取同一个表, 就会多出几行.
    数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题. 
    一个事务与其他事务隔离的程度称为隔离级别. 数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱

    数据库提供的 4 种事务隔离级别:

    Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE. Oracle 默认的事务隔离级别为: READ COMMITED 
    Mysql 支持 4 中事务隔离级别. Mysql 默认的事务隔离级别为: REPEATABLE READ

    具体代码实现:

      1 /**
      2      * ID1 给 ID2 500钱 
      3      * 关于事务:
      4      * 1.如果多个操作,每个使用自己单独的连接,则无法保证事务 例 test1演示
      5      * 2.具体步骤:
      6      *     1) 事务开始前,取消Connection 的默认的自动提交  setAutoCommit(false);
      7      *     2) 如果事务的操作都成功,那么就提交事务
      8      *     3)否则在 try-catch块中回滚
      9      * try {
     10      *      
     11      * conn.setAutoCommit(false);
     12      * ...
     13      *     conn.commit();
     14      * }catch{
     15      * ...
     16      *     conn.rollback();
     17      * }
     18      */
     19     @Test 
     20     public void test2(){
     21         
     22         Connection conn = null;
     23         try {
     24             conn = JDBC_Tools.getConnection();
     25             //System.out.println(conn.getAutoCommit());
     26             
     27             // 1) 取消自动提交
     28             conn.setAutoCommit(false);
     29             
     30             String sql = "UPDATE rent set money = "
     31                     + "money - 500 where id = ?";
     32             
     33             // 2) 如果事务的操作都成功,那么就提交事务
     34             update(conn,sql, 1);
     35             
     36             //int i = 1 / 0; 
     37             
     38             sql = "UPDATE rent set money = "
     39                     + "money + 500 where id = ?";
     40             update(conn,sql, 2);
     41             conn.commit();
     42         } catch (Exception e) {
     43             e.printStackTrace();
     44             
     45             // 3)否则在 try-catch块中回滚
     46             try {
     47                 conn.rollback();
     48             } catch (SQLException e1) {
     49                 e1.printStackTrace();
     50             }
     51             
     52         }finally{
     53             JDBC_Tools.relaseSource(conn, null);
     54         }
     55     }
     56 public static void update(Connection conn,String sql,Object...objs){
     57         
     58         PreparedStatement ps =null;
     59         try {
     60             ps = conn.prepareStatement(sql);
     61             
     62             for(int i = 0;i<objs.length;i++){
     63                 ps.setObject(i+1, objs[i]);
     64             }
     65             ps.executeUpdate();
     66         } catch (Exception e) {
     67             e.printStackTrace();
     68         }finally{
     69             JDBC_Tools.relaseSource(null, ps);
     70         }
     71     }
     72 
     73     @Test
     74     public void test1() {
     75 
     76         String sql = "UPDATE rent set money = "
     77                 + "money - 500 where id = ?";
     78         DAO.update(sql, 1);
     79         
     80         int i = 1 / 0; //一旦出现异常, ID1 减了500,但是 ID2 的钱并没有增加
     81         
     82         sql = "UPDATE rent set money = "
     83                 + "money + 500 where id = ?";
     84         DAO.update(sql, 2);
     85     }设置隔离级别
     86 
     87  public static <E> E getForValue(String sql){
     88         
     89         //1. 得到结果集,该结果只有一行一列
     90         Connection conn = null;
     91         PreparedStatement ps = null;
     92         ResultSet rs = null;
     93         try {
     94             //1. 获取数据库连接
     95             conn = JDBC_Tools.getConnection();//System.out.println(conn.getTransactionIsolation());
     96             conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
     97             //2. 获取 PreparedStatement 对象
     98             ps = conn.prepareStatement(sql);
     99             //2. 取得结果
    100             rs = ps.executeQuery();
    101             if(rs.next()){
    102                 return (E)rs.getObject(1);
    103             }
    104         }catch(Exception e){
    105                 e.printStackTrace();
    106         }finally{
    107         JDBC_Tools.relaseSource(rs,conn, ps);
    108         }
    109         return null;
    110     }

    在 MySql 中设置隔离级别

    具体代码实现:

     1 public static <E> E getForValue(String sql){
     2         
     3         //1. 得到结果集,该结果只有一行一列
     4         Connection conn = null;
     5         PreparedStatement ps = null;
     6         ResultSet rs = null;
     7         try {
     8             //1. 获取数据库连接
     9             conn = JDBC_Tools.getConnection();//System.out.println(conn.getTransactionIsolation());
    10             conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
    11             //2. 获取 PreparedStatement 对象
    12             ps = conn.prepareStatement(sql);
    13             //2. 取得结果
    14             rs = ps.executeQuery();
    15             if(rs.next()){
    16                 return (E)rs.getObject(1);
    17             }
    18         }catch(Exception e){
    19                 e.printStackTrace();
    20         }finally{
    21         JDBC_Tools.relaseSource(rs,conn, ps);
    22         }
    23         return null;
    24     }

    启动一个 mysql 程序, 就会获得一个单独的数据库连接. 每个数据库连接都有一个全局变量 @@tx_isolation, 表示当前的事务隔离级别. MySQL 默认的隔离级别为 Repeatable Read
    查看当前的隔离级别: SELECT @@tx_isolation;
    设置当前 mySQL 连接的隔离级别:  
    set  transaction isolation level read committed;
    设置数据库系统的全局的隔离级别:
    set global transaction isolation level read committed;

    JDBC批量执行

    当需要成批插入或者更新记录时。可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率

    /**
         * 向mysql的testJ数据表中插入100000条记录
         * 测试如何插入用时最短
         * 版本一:使用Statement
         */

    版本一:我们使用Statement进行事务的操作

     1     @Test
     2     public void testBatchWithStatement(){
     3         Connection connection=null;
     4         Statement statement=null;
     5         String sql;
     6         try {
     7             connection=JDBCTools.getConnection();
     8             //放到一个事务里面
     9             JDBCTools.beginTx(connection);
    10             statement=connection.createStatement();
    11             long begin=System.currentTimeMillis();
    12             for(int i=0;i<100000;i++){
    13                 sql="insert into testj values("+
    14                 (i+1)+", 'name_"+ i+"', '2016-05-08')";
    15                 statement.execute(sql);
    16             }
    17             long end=System.currentTimeMillis();
    18             System.out.println("Time:"+(end-begin));
    19             JDBCTools.commit(connection);
    20         } catch (Exception e) {
    21             e.printStackTrace();
    22             JDBCTools.rollback(connection);
    23         }finally{
    24             JDBCTools.release(null, statement, connection);
    25         }
    26     }

    运行结果:

    Time:8991  

    结论一:我们使用Statement插入100000条记录用时8991;

    版本二:我们使用PreparedStatement进行事务的操作

     1 @Test
     2     public void testBatchWithPreparedStatement() {
     3         Connection connection = null;
     4         PreparedStatement preparedStatement = null;
     5         String sql;
     6         try {
     7             connection = JDBCTools.getConnection();
     8             // 放到一个事务里面
     9             JDBCTools.beginTx(connection);
    10             sql = "isnert into testJ values(?,?,?)";
    11             preparedStatement = connection.prepareStatement(sql);
    12             long begin = System.currentTimeMillis();
    13             for (int i = 0; i < 100000; i++) {
    14                 preparedStatement.setInt(1, i + 1);
    15                 preparedStatement.setString(2, "name_" + i);
    16                 preparedStatement.setDate(3,
    17                         new Date(new java.util.Date().getTime()));
    18                 preparedStatement.execute();
    19             }
    20             long end = System.currentTimeMillis();
    21             System.out.println("Time:" + (end - begin));
    22             JDBCTools.commit(connection);
    23         } catch (Exception e) {
    24             e.printStackTrace();
    25             JDBCTools.rollback(connection);
    26         } finally {
    27             JDBCTools.release(null, preparedStatement, connection);
    28         }
    29     }

    运行结果:
    Time:8563

    结论2:因为我这里使用的是mysql数据库进行的操作,插入大量数据的时间性能方面的影响不是很大,如果我们换成oracle数据库或其他大型的关系型数据库,事务执行用时相比版本一的1/4;

    版本三:批处理插入数据

     1 @Test
     2     public void testBatchWithBatch() {
     3         Connection connection = null;
     4         PreparedStatement preparedStatement = null;
     5         String sql=null;
     6         try {
     7             connection = JDBCTools.getConnection();
     8             // 放到一个事务里面
     9             JDBCTools.beginTx(connection);
    10             sql = "insert into testJ values(?,?,?)";
    11             preparedStatement = connection.prepareStatement(sql);
    12             long begin = System.currentTimeMillis();
    13             for (int i = 0; i < 100000; i++) {
    14                 preparedStatement.setInt(1, i + 1);
    15                 preparedStatement.setString(2, "name_" + i);
    16                 preparedStatement.setDate(3,
    17                         new Date(new java.util.Date().getTime()));
    18                 //积攒SQL
    19                 preparedStatement.addBatch();
    20                 //当积攒到一定程度,就统一执行,并且清空先前积攒的SQL
    21                 if((i+1)%300==0){
    22                     //执行
    23                     preparedStatement.executeBatch();
    24                     //清空
    25                     preparedStatement.clearBatch();
    26                 }
    27             }
    28             //如果插入的记录数不是300的整倍数,再执行一次
    29             if(100000%300!=0){
    30                 //执行
    31                 preparedStatement.executeBatch();
    32                 //清空
    33                 preparedStatement.clearBatch();
    34             }
    35             long end = System.currentTimeMillis();
    36             System.out.println("Time:" + (end - begin));
    37             JDBCTools.commit(connection);
    38         } catch (Exception e) {
    39             e.printStackTrace();
    40             JDBCTools.rollback(connection);
    41         } finally {
    42             JDBCTools.release(null, preparedStatement, connection);
    43         }
    44     }

    运行结果:4587(又提高了,但是还是不明显)
    结论三:批处理事务建议采用版本三的方式,再次建议使用oracle数据库做这个插入数据事务的实验,mysql小数据还成,大量的数据也真呵呵了;


    本文为博主原创文章,转载请注明出处:http://www.cnblogs.com/ysw-go/
    1、本博客的原创原创文章,都是本人平时学习所做的笔记,如有错误,欢迎指正。
    2、如有侵犯您的知识产权和版权问题,请通知本人,本人会即时做出处理文章。
    3、本博客的目的是知识交流所用,转载自其它博客或网站,作为自己的参考资料的,感谢这些文章的原创人员

  • 相关阅读:
    一种client同步server数据的方案
    nodejs package.json解释
    node.js JS对象和JSON字符串之间的转换
    setInterval的用法
    ActiveMQ 入门Nodejs版
    ActiveMQ + NodeJS + Stomp 极简入门
    为什么 ++[[]][+[]]+[+[]] = 10?
    Child Process模块
    phantomjs 解码url
    PhantomJSのメモいろいろ
  • 原文地址:https://www.cnblogs.com/ysw-go/p/5470840.html
Copyright © 2011-2022 走看看