无法产生幻读

Unable to produce a phantom read

为了学习,我正在尝试产生幻读,但不幸的是我做不到。我正在使用 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 | 
----------------------------------------------------------

我正在使用: Java 8 JDBC MySQL InnoDB Sakila数据库插入 mysql

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

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

它实际上并不要求必须发生幻读。 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 标准所要求的:如果您重新读取(已删除的)行,您将获得原始值。