【问题标题】:Unable to produce a phantom read无法产生幻读
【发布时间】:2017-08-05 06:52:55
【问题描述】:

为了学习,我正在尝试制作幻读,但不幸的是我做不到。我正在使用 Java 线程、JDBC、MySQL。 这是我正在使用的程序:

package com.isolation.levels.phenomensa;

import javax.xml.transform.Result;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.concurrent.CountDownLatch;

import static com.isolation.levels.ConnectionsProvider.getConnection;
import static com.isolation.levels.Utils.printResultSet;

/**
 * Created by dreambig on 13.03.17.
 */
public class PhantomReads {


    public static void main(String[] args) {


        setUp(getConnection());// delete the newly inserted row, the is supposed to be a phantom row
        CountDownLatch countDownLatch1 = new CountDownLatch(1);  // use to synchronize threads steps
        CountDownLatch countDownLatch2 = new CountDownLatch(1);  // use to synchronize threads steps

        Transaction1 transaction1 = new Transaction1(countDownLatch1, countDownLatch2, getConnection()); // the first runnable
        Transaction2 transaction2 = new Transaction2(countDownLatch1, countDownLatch2, getConnection()); // the second runnable

        Thread thread1 = new Thread(transaction1); // transaction 1
        Thread thread2 = new Thread(transaction2); // transaction 2

        thread1.start();
        thread2.start();

    }

    private static void setUp(Connection connection) {

        try {
            connection.prepareStatement("DELETE from actor where last_name=\"PHANTOM_READ\"").execute();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }


    public static class Transaction1 implements Runnable {


        private CountDownLatch countDownLatch;
        private CountDownLatch countDownLatch2;
        private Connection connection;


        public Transaction1(CountDownLatch countDownLatch, CountDownLatch countDownLatch2, Connection connection) {
            this.countDownLatch = countDownLatch;
            this.countDownLatch2 = countDownLatch2;
            this.connection = connection;
        }

        @Override
        public void run() {


            try {

                String query = "select * from actor where first_name=\"BELA\"";

                connection.setAutoCommit(false); // start the transaction

                // the transaction isolation, dirty reads and non-repeatable reads are prevented !
                // only phantom reads can occure
                connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);

                //read the query result for the first time.
                ResultSet resultSet = connection.prepareStatement(query).executeQuery();
                printResultSet(resultSet);                 // print result.

                //count down so that thread2 can insert a row and commit.
                countDownLatch2.countDown();
                //wait for the second query the finish inserting the row
                countDownLatch.await();

                System.out.println("\n ********* The query returns a second row satisfies it (a phantom read) ********* !");
                //query the result again ... 
                ResultSet secondRead = connection.createStatement().executeQuery(query);

                printResultSet(secondRead);  //print the result

            } catch (SQLException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }


    public static class Transaction2 implements Runnable {


        private CountDownLatch countDownLatch;
        private CountDownLatch countDownLatch2;
        private Connection connection;


        public Transaction2(CountDownLatch countDownLatch, CountDownLatch countDownLatch2, Connection connection) {
            this.countDownLatch = countDownLatch;
            this.countDownLatch2 = countDownLatch2;
            this.connection = connection;
        }


        @Override
        public void run() {

            try {
                //wait the first thread to read the result 
                countDownLatch2.await();

                //insert and commit ! 
                connection.prepareStatement("INSERT INTO actor (first_name,last_name) VALUE (\"BELA\",\"PHANTOM_READ\") ").execute();
                //count down so that the thread1 can read the result again ... 
                countDownLatch.countDown();
            } catch (SQLException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }


    }


}

然而这实际上是结果

----------------------------------------------------------
 | 196 |  | BELA |  | WALKEN |  | 2006-02-15 04:34:33.0 | 
---------------------------------------------------------- The query returns a second row satisfies it (a phantom read)
----------------------------------------------------------
 | 196 |  | BELA |  | WALKEN |  | 2006-02-15 04:34:33.0 | 
----------------------------------------------------------

但我觉得应该是的

----------------------------------------------------------
 | 196 |  | BELA |  | WALKEN |  | 2006-02-15 04:34:33.0 | 
---------------------------------------------------------- The query returns a second row satisfies it (a phantom read)  !
----------------------------------------------------------
 | 196 |  | BELA |  | WALKEN |  | 2006-02-15 04:34:33.0 | 
----------------------------------------------------------
----------------------------------------------------------
 | 196 |  | BELA |  | PHANTOM_READ |  | 2006-02-15 04:34:33.0 | 
----------------------------------------------------------

我正在使用: 爪哇 8 JDBC MySQL InnoDB SakilaDB 插入 mysql

【问题讨论】:

    标签: java mysql jdbc transactions isolation-level


    【解决方案1】:

    幻读是以下场景:事务读取一组满足搜索条件的行。然后第二个事务插入满足此搜索条件的行。然后第一个事务再次读取满足搜索条件的行集,并获得不同的行集(例如,包括新插入的行)。

    可重复读取 要求如果一个事务读取一行,另一个事务随后更新或删除该行并提交这些更改,并且第一个事务重新读取该行,它将获得相同的常量值和以前一样(快照)。

    它实际上并不要求必须发生幻读。 MySQL 实际上会在比它必须的更多情况下防止幻读。在 MySQL 中,幻读(当前)仅在您(意外)更新幻像行之后发生,否则该行将保持隐藏状态。这是特定于 MySQL 的,其他数据库系统的行为会有所不同。此外,这种行为可能有一天会改变(因为 MySQL 仅指定它支持 sql 标准要求的一致性读取,而不是在特定情况下发生幻像读取)。

    您可以使用例如以下步骤来获取虚拟行:

    insert into actor (first_name,last_name) values ('ADELIN','NO_PHANTOM');
    
    transaction 1:
    select * from actor;
    -- ADELIN|NO_PHANTOM
    
    transaction 2:
    insert into actor (first_name,last_name) values ('BELA','PHANTOM_READ');
    commit;
    
    transaction 1:
    select * from actor;  -- still the same
    -- ADELIN|NO_PHANTOM
    
    update actor set last_name = 'PHANTOM READ'
    where last_name = 'PHANTOM_READ';
    
    select * from actor;  -- now includes the new, updated row
    -- ADELIN|NO_PHANTOM
    -- BELA  |PHANTOM READ
    

    另外一件有趣的事情发生在你删除行时:

    insert into actor (first_name,last_name) values ('ADELIN','NO_PHANTOM');
    insert into actor (first_name,last_name) values ('BELA','REPEATABLE_READ');
    
    transaction 1:
    select * from actor; 
    -- ADELIN|NO_PHANTOM
    -- BELA  |REPEATABLE_READ
    
    transaction 2:
    delete from actor where last_name = 'REPEATABLE_READ';
    commit;
    
    transaction 1:      
    select * from actor;  -- still the same 
    -- ADELIN|NO_PHANTOM
    -- BELA  |REPEATABLE_READ
    
    update actor set last_name = '';
    
    select * from actor;  -- the deleted row stays unchanged
    -- ADELIN|
    -- BELA  |REPEATABLE_READ
    

    这正是 sql 标准所要求的:如果您重新读取(删除)行,您将获得原始值。

    【讨论】:

      猜你喜欢
      • 2011-07-23
      • 2017-02-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-01
      相关资源
      最近更新 更多