Как производить phantom чтение?
Используя "повторяемое чтение", должно быть возможно выполнить чтение phantom, но как? Мне нужно это для примера обучения студентов CS.
Я думаю, что я должен сделать "SELECT... WHERE x <= 888" в неиндексированном поле x, с верхним пределом 888 нет, а затем на другом соединении вставить новую строку со значением чуть ниже 888.
Кроме того, это не сработает. Нужен ли мне очень большой стол? Или что-то еще?
Ответы
Ответ 1
Эрик,
Я пришел просто из теста с очень большим количеством строк.
Вы никогда не найдете фантомы в mysql InnoDB с прочитанным или более ограниченным уровнем изоляции. Это объясняется документацией:
REPEATABLE READ: для последовательных чтений существует существенное отличие от уровня изоляции READ COMMITTED: все согласованные чтения в рамках одной транзакции считывают моментальный снимок, созданный с помощью первого прочитанного. Это соглашение означает, что если вы выдаете несколько простых (неблокирующих) операторов SELECT в рамках одной транзакции, эти операторы SELECT также согласуются друг с другом. См. Раздел 13.6.8.2, "Согласованные неблокирующие чтения".
Но вы также не можете найти фантомы в прочитанном фиксированном уровне изоляции: это необходимо, потому что "phantom rows" должен быть заблокирован для репликации и восстановления MySQL для работы.
Более подробная информация: http://dev.mysql.com/doc/refman/5.1/en/set-transaction.html
Думаю, вам нужно будет перейти на другой бренд базы данных, чтобы показать фантомы своим ученикам. Я использую MSSQLSERVER и Oracle.
Хорошо... его жаль на ваш первый вопрос.
Ответ 2
Возможность воспроизведения phantom читает для движка InnoDB для уровня изоляции REPEATABLE READ сомнительно, потому что InnoDB использует Multiversion concurrency control - для каждой строки двигатель MVCC знает номера транзакций, когда строка была вставлена и удалена, и может воспроизводить историю обновлений строк.
Таким образом, все последующие операторы SELECT будут отображать состояние таблицы в начале транзакции, за исключением строк, которые были вставлены, удалены или обновлены одной и той же транзакцией. Никакие новые строки, совершенные другими транзакциями, не появятся, потому что у них будут номера транзакций ввода, превышающие число транзакций этой транзакции, и диапазон строк не имеет значения.
Я смог воспроизвести phantom READS для уровня изоляции REPEATABLE READ для Apache Derby, потому что он не используйте multiversion concurrency control (версия 10.8.2.2 в момент написания этого ответа).
Чтобы воспроизвести, установите правильный уровень транзакции (в ij - Derby SQL-клиент):
-- Set autocommit off
autocommit off;
-- Set isolation level corresponding to ANSI REPEATABLE READ
set isolation rs;
Т1:
SELECT * FROM TableN;
Т2:
INSERT INTO TableN VALUES(55, 1);
COMMIT;
T1 снова:
SELECT * FROM TableN;
Теперь T1 должен увидеть еще одну строку;
Ответ 3
InnoDB должен защищать от чтения phantom, как писали другие.
Но у InnoDB есть другое странное поведение, связанное с блокировкой. Когда запрос получает блокировку, он всегда получает блокировку самой последней версии строки. Поэтому попробуйте следующее
CREATE TABLE foo (i INT PRIMARY KEY, val INT);
INSERT INTO foo (i, val) VALUES (1, 10), (2, 20), (3, 30);
Затем в двух параллельных сеансах (откройте два окна терминала):
-- window 1 -- window 2
START TRANSACTION;
START TRANSACTION;
SELECT * FROM foo;
UPDATE foo SET val=35 WHERE i=3;
SELECT * FROM foo;
Это должно показать val = 10, 20, 30 в обоих SELECT, так как REPEATABLE-READ означает, что второе окно видит только данные, которые существовали при его запуске.
Однако:
SELECT * FROM foo FOR UPDATE;
Второе окно ожидает получения блокировки в строке 3.
COMMIT;
Теперь SELECT во втором окне заканчивается и показывает строки с val = 10, 20, 35, потому что блокировка строки приводит к тому, что SELECT видит самую последнюю зафиксированную версию. Блокирующие операции в InnoDB действуют так, как будто они выполняются под READ-COMMITTED, независимо от уровня изоляции транзакции.
Вы можете даже переключаться назад и вперед:
SELECT * FROM foo;
SELECT * FROM foo FOR UPDATE;
SELECT * FROM foo;
SELECT * FROM foo FOR UPDATE;
Ответ 4
Phantom чтение может происходить из-за отсутствия запретов диапазона, тогда пример (псевдокод):
Резьба1
Transaction 1
Update TableN set X=2 where X=1
wait(s1)
Select TableN where X=1
Commit
thread2
Transaction 2:
insert into tableN(id, X) values(55,1)
commit;
notify(s1)
В wikipedia есть еще один пример phantom: Phantom Reads | wikipedia
Здесь важна синхронизация транзакций, вы можете использовать точки синхронизации.
EDIT Пример использования функции сна mysql (не проверен):
--on thread 1
Create TableN(id int, x int);
insert into TableN(id, X) values(1,1);
insert into TableN(id, X) values(2,1);
insert into TableN(id, X) values(3,1);
BEGIN TRANSACTION;
Update TableN set X=2 where X=1
SELECT SLEEP(30) FROM DUAL;
select TableN from where X=1;
COMMIT;
--In other thread, before 20 secs;
BEGIN TRANSACTION;
insert into TableN(id, X) values(55,1);
COMMIT;
Ответ 5
Чтобы дополнить Дани хороший ответ, вы можете использовать Microsoft Sql Server, чтобы показать это поведение своим ученикам.
Сервер Sql показывает, что phantom читает на уровне повторяемости чтения, как указано в документации здесь.
Postgres подписывается на то же понятие, что и InnoDb, как описано здесь. В Postgres также нет чтения phantom в повторяемом чтении и, следовательно, также не подходит для вашей дидактической цели.
Sql Server предлагает еще один уровень изоляции, моментальный снимок, который делает то, что MySql InnoDb и Postgres делает в повторяемом чтении (которое представляет собой незакрепленную версию на основе версии повторяемого чтения без phantom, но не сериализуется).
Sql Server Express бесплатный, хотя вам нужен компьютер с Windows. Вы также можете получить учетную запись Windows Azure и показать это поведение в Sql Azure онлайн.
Ответ 6
"phantom read" в MySQL на уровне изоляции RR скрыт глубоко, но все же может воспроизвести его. Вот шаги: