无法生成幻像读取

时间:2017-03-14 19:01:17

标签: java mysql jdbc transactions isolation-level

我正在尝试制作一个幻像读物,为了学习,但不幸的是我无法做到。我正在使用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 DB插入mysql

1 个答案:

答案 0 :(得分:2)

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

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

实际上并不要求幻像读取必须发生。 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

当您删除行时,btw会发生另一件有趣的事情:

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标准所要求的:如果你重读(删除)行,你将获得原始值。