С приходом master-master репликаций остро встает вопрос о целостность с достоверностью базы данных.
  • Целостность базы данных - соответствие имеющейся в базе данных информации её внутренней логике, структуре и всем явно заданным правилам.
  • Достоверность (или истинность) - соответствие фактов, хранящихся в базе данных, реальному миру

При изменении данных, БД переходит от одного состояния к другому, при этом в процессе обновления данных возможны ситуации, когда состояние целостности или достоверности нарушается.

Например:

Прерванный перевод денег со счета на счет, посредством последовательного исполнения двух команд UPDATE, приведет к нарушению целостности:

Чтобы избежать подобные ситуации ввели понятие транзакции — атомарного действия над базой, переводящего ее из одного целостного состояния в другое. По сути это последовательность SQL-инструкций, которые должны быть выполнены целиком или отменены.

Механизмы блокировок

Одновременное чтение одним клиентом и запись другим клиентом одной и той же строки таблицы с большой вероятностью приведет к сбою или чтению некорректных данных. Механизмы блокировок позволяют избежать ситуаций одновременного доступа к данным, регламентируя порядок взаимодействия пользователей между собой. Способы реализации механизма блокировок в СУБД различных производителей могут существенно отличаться, однако суть примерно одинаковая:

Если для выполнения некоторой транзакции необходимо, чтобы некоторый объект базы данных не изменялся без ведома этой транзакции, такой объект блокируется.

Основными видами блокировок являются на чтение и на запись:

  • Если клиент хочет читать данные, то другие клиенты тоже могут читать данные, но никто не может записывать, пока первый клиент не закончит чтение (read lock ).
  • Если клиент хочет записать данные, то другие клиенты не должны ни читать ни писать эти данные пока первый клиент не закончит (write lock ).

Блокировка может быть наложена явно или неявно .

Если клиент не назначает блокировку, MySQL сервер неявно устанавливает необходимый тип блокировки на время выполнения выражения или транзакции. В случае выполнения оператора SELECT сервер установит READ LOCK, а в случае UPDATE - WRITE LOCK. При неявной блокировке уровень блокировки зависит от типа хранилища данных: для MyISAM, MEMORY и MERGE блокируется вся таблица, для InnoDB - только используемые в выражении строки (в случае, если набор этих строк может быть однозначно определен - иначе, блокируется вся таблица).

Часто возникает необходимость выполнения нескольких запросов подряд без вмешательства других клиентов в это время. Неявная блокировка не подходит для этих целей, так как устанавливается только на время выполнения одного запроса. В этом случае клиент может явно назначить, а потом отменить блокировку с помощью выражений LOCK TABLES и UNLOCK TABLES. Явной блокировка всегда блокирует всю таблицу, независимо от механизма хранения.

Изоляция транзакций

Теоретически СУБД должна обеспечивать полную изоляцию транзакций. На практике существует несколько уровней изоляции при которых в транзакции допускаются несогласованные данные. Более высокий уровень изолированности повышает точность данных, но при этом может снижаться количество параллельно выполняемых транзакций. Более низкий уровень изолированности позволяет выполнять больше параллельных транзакций, но снижает точность данных.

При параллельном выполнении транзакций возможны следующие проблемы:

1) Потерянное обновление (англ. lost update)

При одновременном изменении одного блока данных разными транзакциями, одно из изменений теряется;

Имеются две транзакции, выполняемые одновременно:

Транзакция 1 Транзакция 2
UPDATE tbl1 SET f2=f2+20 WHERE f1=1; UPDATE tbl1 SET f2=f2+25 WHERE f1=1;

В обеих транзакциях изменяется значение поля f2, при этом одно из изменений теряется. Так что, f2 будет увеличено не на 45, а только на 20 или 25.

Причина :

  1. Первая транзакция прочитала текущее состояние поля.
  2. Вторая транзакция сделала свои изменения, основываясь на своих сохраненных в память данных.
  3. Первая делает обновление поля, используя свои «старые» данные.

2) «Грязное» чтение (англ. dirty read)

Чтение данных, добавленных или изменённых транзакцией, которая впоследствии не подтвердится (откатится);

Транзакция 1 Транзакция 2
SELECT f2 FROM tbl1 WHERE f1=1;
ROLLBACK WORK;

В транзакции 1 изменяется значение поля f2, а затем в транзакции 2 выбирается значение этого поля. После этого происходит откат транзакции 1. В результате значение, полученное второй транзакцией, будет отличаться от значения, хранимого в базе данных.

3) Неповторяющееся чтение (англ. non-repeatable read)

При повторном чтении в рамках одной транзакции, ранее прочитанные данные оказываются изменёнными.

Предположим, имеются две транзакции, открытые различными приложениями, в которых выполнены следующие SQL-операторы:

Транзакция 1 Транзакция 2
SELECT f2 FROM tbl1 WHERE f1=1; SELECT f2 FROM tbl1 WHERE f1=1;
UPDATE tbl1 SET f2=f2+1 WHERE f1=1;
COMMIT;
SELECT f2 FROM tbl1 WHERE f1=1;

В транзакции 2 выбирается значение поля f2, затем в транзакции 1 изменяется значение поля f2. При повторной попытке выбора значения из поля f2 в транзакции 2 будет получен другой результат. Эта ситуация особенно неприемлема, когда данные считываются с целью их частичного изменения и обратной записи в базу данных.

4) Фантомное чтение (англ. phantom reads)

Одна транзакция в ходе своего выполнения несколько раз выбирает множество строк по одним и тем же критериям. Другая транзакция в интервалах между этими выборками добавляет или удаляет строки или изменяет столбцы некоторых строк, используемых в критериях выборки первой транзакции, и успешно заканчивается. В результате получится, что одни и те же выборки в первой транзакции дают разные множества строк.

Транзакция 1 Транзакция 2
SELECT SUM(f2) FROM tbl1;
INSERT INTO tbl1 (f1,f2) VALUES (15,20);
COMMIT;
SELECT SUM(f2) FROM tbl1;

В транзакции 2 выполняется SQL-оператор, использующий все значения поля f2. Затем в транзакции 1 выполняется вставка новой строки, приводящая к тому, что повторное выполнение SQL-оператора в транзакции 2 выдаст другой результат. Такая ситуация называется фантомным чтением. От неповторяющегося чтения оно отличается тем, что результат повторного обращения к данным изменился не из-за изменения/удаления самих этих данных, а из-за появления новых (фантомных) данных.

Уровни изоляции

Serializable (упорядочиваемость)

Самый высокий уровень изолированности. Транзакции полностью изолируются друг от друга. Только на этом уровне параллельные транзакции не подвержены эффекту «фантомного чтения».

Repeatable read (повторяемость чтения)

Уровень, при котором чтение одной и той же строки или строк в транзакции дает одинаковый результат. (Пока транзакция не завершена, никакие другие транзакции не могут модифицировать эти данные.)

Read committed (чтение фиксированных данных)

Завершенное чтение, при котором отсутствует черновое, «грязное» чтение (то есть чтение одним пользователем данных, которые не были зафиксированы в БД командой COMMIT). Тем не менее в процессе работы одной транзакции другая может быть успешно завершена и сделанные ею изменения зафиксированы. В итоге первая транзакция будет работать с другим набором данных. Это проблема неповторяемого чтения.

Read uncommitted (чтение незафиксированных данных)

Низший уровень изоляции, соответствующий нулевому уровню. Он гарантирует только отсутствие потерянных обновлений. Если несколько транзакций одновременно пытались изменять одну и ту же строку, то в окончательном варианте строка будет иметь значение, определенное последней успешно выполненной транзакцией.

Поведение при различных уровнях изолированности

В стандарте SQL описывается четыре уровня изоляции транзакций - Read uncommited (Чтение незафиксированных данных), Read committed (Чтение зафиксированных данных), Repeatable read (Повторяемое чтение) и Serializable (Сериализуемость). В данной статье будет рассмотрен жизненный цикл четырёх параллельно выполняющихся транзакций с уровнями изоляции и .


Для уровня изоляции Read committed допустимы следующие особые условия чтения данных:


Неповторяемое чтение - транзакция повторно читает те же данные, что и раньше, и обнаруживает, что они были изменены другой транзакцией (которая завершилась после первого чтения).


Фантомное чтение - транзакция повторно выполняет запрос, возвращающий набор строк для некоторого условия, и обнаруживает, что набор строк, удовлетворяющих условию, изменился из-за транзакции, завершившейся за это время.


Что же касается Serializable, то данный уровень изоляции самый строгий, и не имеет феноменов чтения данных.

ACID или 4 свойства транзакций

Прежде чем приступим к рассмотрению уровней изоляции транзакции в паре слов вспомним об основных требованиях к транзакционной системе.


Atomicity (атомарность) - выражается в том, что транзакция должна быть выполнена в целом или не выполнена вовсе.


Consistency (согласованность) - гарантирует, что по мере выполнения транзакций, данные переходят из одного согласованного состояния в другое, то есть транзакция не может разрушить взаимной согласованности данных.


Isolation (изолированность) - локализация пользовательских процессов означает, что конкурирующие за доступ к БД транзакции физически обрабатываются последовательно, изолированно друг от друга, но для пользователей это выглядит, как будто они выполняются параллельно.


Durability (долговечность) - устойчивость к ошибкам - если транзакция завершена успешно, то те изменения в данных, которые были ею произведены, не могут быть потеряны ни при каких обстоятельствах.

Уровень изоляции Read Committed

По умолчанию в PostgreSQL уровень изоляции Read Committed. Такой уровень изоляции всегда позволяет видеть изменения внесённые успешно завершёнными транзакциями в оставшихся параллельно открытых транзакциях. В транзакции, работающей на этом уровне, запрос SELECT (без предложения FOR UPDATE/SHARE) видит только те данные, которые были зафиксированы до начала запроса; он никогда не увидит незафиксированных данных или изменений, внесённых в процессе выполнения запроса параллельными транзакциями. По сути запрос SELECT видит снимок базы данных в момент начала выполнения запроса. Однако SELECT видит результаты изменений, внесённых ранее в этой же транзакции, даже если они ещё не зафиксированы. Также заметьте, что два последовательных оператора SELECT могут видеть разные данные даже в рамках одной транзакции, если какие-то другие транзакции зафиксируют изменения после выполнения первого SELECT.


Суть уровня изоляции Read Committed показана на диаграмме 1.


Примечание: В таблице уже находится запись с первой версией данных (v1). Прошу воспринимать команды SELECT v1; - как команду возвращающую данные версии v1, а UPDATE v1 to v2; - как команду обновления данных с первой версии до второй.


Создадим к базе-данных 4 подключения и откроем в каждом из подключений по транзакции с уровнем изоляции Read Committed



Шаг 1. В начальный момент времени до каких-либо изменений данных всем транзакциям доступна изначальная версия данных (v1);




Шаг 4. Закрытие Первой транзакции. Все изменения сделанные в ходе её работы успешно фиксируются;


Шаг 5. После закрытия Первой транзакции (предыдущий шаг), изменения сделанные в ходе её выполнения над данными (обновление с v1 до v2) были распространены на остальные транзакции, SELECT запрос в оставшихся 3 открытых транзакциях возвращает v2 («Неповторяемое чтение», отличите уровня изоляции Read Committed от Serializable);


Шаг 6. Запрос на обновление данных во Второй транзакции до «третьей версии» успешно выполняется, но запросы на обновление данных блокируют изменяемые строки на дальнейшее их изменение, до завершения Второй транзакции;


Шаг 7. Из-за блокировки наложенной на данные в предыдущем шаге, Третья транзакция переходит в режим ожидания с запросом на удаление данных. Ожидание Третьей транзакции будет происходить до закрытия Второй транзакции;


Шаг 8. Несмотря на то, что Третья транзакция ожидает закрытия Второй, как Вторая так и Четвёртая транзакции без каких либо проблем продолжают свою работу, возвращая данные согласно своим версиям. Вторая возвращает v3, Четвёртая возвращает v2;


Шаг 9. Закрытие Второй транзакции приводит к разблокированию данных для изменения. Уровень изоляции Read Committed позволяет продолжить работу Третьей транзакции без вызова ошибки. Получив доступ на изменение новой версии данных (v3) Третья транзакция УСПЕШНО тут же их «удаляет» (отличие Read Committed от Serializable);


Шаг 10. До закрытия Третьей транзакции, данные будут удалёнными только внутри Третьей транзакции. Четвёртой транзакции до закрытия Третьей данные доступны (SELECT запрос в Четвёртой транзакции возвращает v3);


Шаг 11. Закрытие Третьей транзакции. Все изменения сделанные в ходе её работы успешно фиксируются;


Шаг 12. Запрос на получение данных в Четвёртой транзакции ничего не возвращает («Фантомное чтение», SELECT запрос возвращает 0 записей).


Примечание. На диаграмме не показано действие запроса INSERT. В рамках данного уровня изоляции, строки добавленные, например в шаге 3, в Первой транзакции, были бы ВИДНЫ остальным транзакциям после завершения Первой транзакции.


Частичная изоляция транзакций, обеспечиваемая в режиме Read Committed, приемлема для множества приложений. Этот режим быстр и прост в использовании, однако он подходит не для всех случаев. Приложениям, выполняющим сложные запросы и изменения, могут потребоваться более строго согласованное представление данных, например Serializable.

Уровень изоляции Serializable

Изоляция уровня Serializable обеспечивает беспрепятственный доступ к базе данных транзакциям с SELECT запросами. Но для транзакций с запросами UPDATE и DELETE, уровень изоляции Serializable не допускает модификации одной и той же строки в рамках разных транзакций. При изоляции такого уровня все транзакции обрабатываются так, как будто они все запущены последовательно (одна за другой). Если две одновременные транзакции попытаются обновить одну и туже строку, то это будет не возможно. В таком случае PostgreSQL принудит транзакцию, вторую, да и все последующие, что пытались изменить строку к отмене (откату - ROLLBACK).


Суть уровня изоляции Serializable показана на диаграмме 2.


Создадим к базе-данных 4 подключения и откроем в каждом из подключений по транзакции с уровнем изоляции Serializable



Шаг 1. Всем транзакциям доступна изначальная версия данных (v1);


Шаг 2. В ходе работы Первой транзакции данные без каких либо блокировок успешно обновляются до «второй версии» (v2);


Шаг 3. Изменения сделанные в Первой транзакции будут видны только ей самой (SELECT возвращает v2), и не будут доступны остальным транзакциям (SELECT запрос во Второй и Четвёртой транзакциях возвращает v1);


Шаг 4. Запрос на обновление данных в первой транзакции (шаг 2), блокирует обновляемые строки, и переводит в режим ожидания Вторую транзакцию с запросом на удаление данных. Блокировка транзакций на обновляемые данных будет происходить до закрытия Первой транзакции;


Шаг 5. Несмотря на то, что Вторая транзакция ожидает закрытия Первой, как Третья так и Четвёртая транзакции без каких либо проблем продолжают свою работу, возвращая данные согласно своим версиям;


Шаг 6. Завершение Первой транзакции снимает блокировку с обновляемых данных, но в рамках уровня изоляции Serializable повторное обновление данных в параллельных транзакциях запрещено, и поэтому в ходе выполнения Второй транзакции возникает ошибка (отличие Serializable от Read Committed);


Шаг 7. Запрос SELECT во Второй транзакции становится не возможным, так как ошибка возникшая на предыдущем шаге отменяет («блокирует») транзакцию. Запрос SELECT в Третьей и Четвертой транзакциях возвращают первоначальную версию данных (v1). Несмотря на то, что Первая транзакция была завершена успешно, изменения не стали видны остальным открытым транзакциям (отличие Serializable от Read Committed). Открытие Пятой транзакции в левом верхнем окне;


Шаг 8. Закрытие Второй транзакции. Все изменения сделанные данной транзакцией будут отменены, из-за возникшей ошибки в ходе её работы;


Шаг 9. Запрос SELECT в Пятой транзакции возвращает новую версию данных (v2). Запрос SELECT в Третьей и Четвёртой транзакциях возвращают первоначальную версию данных (v1);


Шаг 10. Уровень изоляции Serializable всё также не даёт обновлять данные, запрос UPDATE в Третьей транзакции завершается не удачно, с вытекающими последствиями для хода всей транзакции (несмотря на то, что Первая транзакция уже удачно завершилась, и все внесённые ей изменения сохранены в базе данных). А вот запрос UPDATE в Пятой транзакции завершается успешно, так как она открыта после завершения Первой транзакции, и работает с новой версией данных;


Шаг 11. Закрытие Третьей транзакции. Все изменения сделанные данной транзакцией будут отменены, из-за возникшей ошибки в ходе её работы;


Шаг 12. Транзакция Четыре всё также показывает, что у транзакций с SELECT запросами никаких нет проблем, а Пятая транзакция получает уже обновлённые же собой данные (v5).


Примечание. На диаграмме не показано действие запроса INSERT. В рамках данного уровня изоляции, строки добавленные, например в шаге 3, в Первой транзакции, были бы НЕ ДОСТУПНЫ Второй, Третьей и Четвёртой транзакциям после завершения Первой транзакции. Также на диаграмме не показан результат ROLLBACK (Шаги 8 и 11). В случае если бы Вторая и Третья транзакции делали какие либо изменения над не заблокированными данными, то все эти изменения не были бы зафиксированы, так как транзакции завершаются неудачно (суть свойства - Atomicity).


Уровень изоляции Serializable гарантирует, что все затронутые в транзакции данные не будут изменены другими транзакциями. На этом уровне появление "фантомов" исключается, поэтому становятся возможными сложные конкурентные операции. На практике такой уровень изоляции требуется в учетных системах.


Для транзакций содержащих только SELECT запросы, использование уровня изоляции Serializable оправдывает себя тогда, когда вы не хотите видеть внесённые изменения параллельно завершёнными транзакциями в ходе работы текущей транзакции.

Аномалия сериализации (Потерянное обновление)

Ещё один феномен чтения данных, описывается тем, что результат успешной фиксации группы транзакций оказывается несогласованным при всевозможных вариантах исполнения этих транзакций по очереди.


Сориентируйте, пожалуйста, меня в комментариях, если я заблуждаюсь насчёт того, что аномалия сериализации и потерянное обновление связанные между собой феномены.


Документация на сайте PostgreSQL PRO пишет, что Read Committed допускает «Serialization Anomaly» . Отечественная Wikipedia, не настаивая на то, что таблица относится именно к PostgreSQL, пишет, что Read Commited предотвращает аномалию сериализации . Английская Википедиа о таком феномене чтения данных умалчивает . Но немецкая Википедия приводит в своей версии таблицы феномен «Lost Updates» , указывая на то, что Read Committed может быть не подвержен потере обновлений с дополнительной защитой через курсор (Cursor Stability). Украинская Википедия поддерживает русскоязычную версию статьи, испанская Википедия поддерживает английскую версию статьи. Англоязычная документация по PostgreSQL не отличается от документации с сайта PostgreSQL PRO.


Cursor Stability расширяет блокировочное поведение уровня READ COMMITED для SQL-курсоров, добавляя новую операцию чтения (Fetch) по курсору rc (означает read cursor, т.е. чтение по курсору) и требуя, чтобы блокировка устанавливалась на текущем элементе курсора. Блокировка удерживается до тех пор, пока курсор не будет перемещен (пока не измениться его текущий элемент) или закрыт, возможно, операцией фиксации. Естественно, транзакция, читающая по курсору, может изменить текущую строку (wc – запись по курсору), и в этом случае блокировка по записи этой строки будет сохраняться до тех пор, пока транзакция не зафиксируется, даже после передвижения курсора с последующей выборкой следующей строки.

Вот такой результат получился в PostgreSQL 9.6


Заключение

Понимание уровней изоляции транзакций является важным аспектом при обработке данных в любой многопользовательской СУБД. Уровни изоляции обладают четко определенными характеристиками и поведением. Более высокие уровни изоляции уменьшают возможности параллельной обработки данных и повышают риск взаимной блокировки процессов. Поэтому корректное использование уровней в зависимости от задач приложений всегда является выбором разработчика в зависимости от требований к обеспечению логической целостности данных, к скорости и к возможности параллельной многопользовательской обработки.

Литература

Только зарегистрированные пользователи могут участвовать в опросе. Войдите , пожалуйста.

Управляет поведением блокировки и версиями строк инструкций Transact-SQL, выданных при соединении с SQL Server.

Синтаксис

SET TRANSACTION ISOLATION LEVEL { READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SNAPSHOT | SERIALIZABLE } [ ; ]

Аргументы

    READ UNCOMMITTED
    Указывает, что инструкции могут считывать строки, которые были изменены другими транзакциями, но еще не были зафиксированы.

    Транзакции, работающие на уровне READ UNCOMMITTED, не используют совмещаемые блокировки, чтобы предотвратить изменение считываемых текущей транзакцией данных другими транзакциями. Транзакции READ UNCOMMITTED также не блокируются монопольными блокировками, которые не позволили бы текущей транзакции считывать измененные другими транзакциями, но не зафиксированные строки. Установка этого параметра позволяет считывать незафиксированные изменения, которые называются чтением«грязных» данных. Значения в данных могут быть изменены и до окончания транзакции строки могут появляться и исчезать в наборе данных. Этот параметр действует так же, как и настройка NOLOCK всех таблиц во всех инструкциях SELECT в транзакции. Это наименьшее ограничение уровней изоляции.

    В SQL Server конфликты блокировок при защите транзакций от чтения «грязных» данных незафиксированных изменений данных можно сократить с помощью следующего:

    • уровня изоляции READ COMMITTED с параметром базы данных READ_COMMITTED_SNAPSHOT, находящимся в состоянии ON;

      уровня изоляции моментального снимка (SNAPSHOT).

    READ COMMITTED
    Указывает, что инструкции не могут считывать данные, которые были изменены другими транзакциями, но еще не были зафиксированы. Это предотвращает чтение«грязных» данных. Данные могут быть изменены другими транзакциями между отдельными инструкциями в текущей транзакции, результатом чего будет неповторяемое чтение или фантомные данные. Этот параметр в SQL Server установлен по умолчанию.

    Поведение READ COMMITTED зависит от настройки аргумента базы данных READ_COMMITTED_SNAPSHOT.

    • Если параметр READ_COMMITTED_SNAPSHOT находится в состоянии OFF (по умолчанию), компонент Компонент Database Engine при выполнении операций считывания текущей транзакцией использует совмещаемые блокировки для предотвращения изменения строк другими транзакциями. Совмещаемые блокировки также блокируют инструкции от считывания строк, измененных другими транзакциями, пока не завершится другая транзакция. От типа совмещаемой блокировки зависит время ее освобождения. Блокировка строки освобождается перед обработкой следующей строки. Блокировка строки освобождается при чтении следующей страницы, а блокировка таблицы освобождается при завершении выполнения инструкции.

      Примечание

      Если параметр READ_COMMITTED_SNAPSHOT находится в состоянии ON, компонент Компонент Database Engine использует управление версиями строк для представления каждой инструкции согласованного на уровне транзакций моментального снимка данных в том виде, который они имели на момент начала выполнения инструкции. Для защиты данных от обновления другими транзакциями блокировки не используются.

      Изоляция моментальных снимков поддерживает данные FILESTREAM.В режиме изоляции моментальных снимков данные FILESTREAM, считанные любой инструкцией транзакции, будут согласованы на уровне транзакции с версией данных, существовавших на момент начала транзакции.

    Если параметр базы данных READ_COMMITTED_SNAPSHOT имеет значение ON, для запроса совмещаемой блокировки можно использовать табличное указание READCOMMITTEDLOCK вместо управления версиями строк для отдельных инструкций в транзакциях, работающих на уровне изоляции READ COMMITTED.

    Примечание

    При установке параметра READ_COMMITTED_SNAPSHOT разрешается только то соединение с базой данных, которое выполняет команду ALTER DATABASE.До завершения инструкции ALTER DATABASE в базе данных не должно быть других открытых соединений.База данных не обязательно должна находиться в однопользовательском режиме.

    REPEATABLE READ
    Указывает на то, что инструкции не могут считывать данные, которые были изменены, но еще не зафиксированы другими транзакциями, а также на то, что другие транзакции не могут изменять данные, читаемые текущей транзакцией, до ее завершения.

    Совмещаемые блокировки применяются ко всем данным, считываемым любой инструкцией транзакции, и сохраняются до ее завершения. Это запрещает другим транзакциям изменять строки, считываемые текущей транзакцией. Другие транзакции могут вставлять новые строки, соответствующие условиям поиска инструкций, содержащихся в текущей транзакции. При повторном запуске инструкции текущей транзакцией будут извлечены новые строки, что приведет к фантомному чтению. Учитывая то, что совмещаемые блокировки сохраняются до завершения транзакции и не снимаются в конце каждой инструкции, степень совпадений ниже, чем при уровне изоляции по умолчанию READ COMMITTED. Используйте этот параметр только в случае необходимости.

    SNAPSHOT
    Указывает на то, что данные, считанные любой инструкцией транзакции, будут согласованы на уровне транзакции с версией данных, существовавших в ее начале. Транзакция распознает только те изменения, которые были зафиксированы до ее начала. Инструкции, выполняемые текущей транзакцией, не видят изменений данных, произведенных другими транзакциями после запуска текущей транзакции. Таким образом достигается эффект получения инструкциями в транзакции моментального снимка зафиксированных данных на момент запуска транзакции.

    Транзакции моментальных снимков не требуют блокировки при считывании данных, за исключением случаев восстановления базы данных. Считывание данных транзакциями моментальных снимков не блокирует запись данных другими транзакциями. Транзакции, осуществляющие запись данных, не блокируют считывание данных транзакциями моментальных снимков.

    На этапе отката восстановления базы данных транзакция моментальных снимков запросит блокировку, если будет предпринята попытка считывания данных, заблокированных другой откатываемой транзакцией. Блокировка транзакции моментальных снимков сохраняется до завершения отката. Блокировка снимается сразу после предоставления.

    Перед запуском транзакции, использующей уровень изоляции моментальных снимков, необходимо установить параметр базы данных ALLOW_SNAPSHOT_ISOLATION в ON. Если транзакция с уровнем изоляции моментального снимка обращается к данным из нескольких баз данных, аргумент ALLOW_SNAPSHOT_ISOLATION должен быть включен в каждой базе данных.

    Невозможно изменить на уровень изоляции моментального снимка уровень изоляции транзакции, запущенной с другим уровнем изоляции; в этом случае транзакция будет прервана. Если транзакция запущена с уровнем изоляции моментальных снимков, ее уровень изоляции можно изменять. Транзакция начинается в момент первого доступа к данным.

    Транзакция, работающая с уровнем изоляции моментального снимка, может просматривать внесенные ею изменения. Например, если транзакция выполняет инструкцию UPDATE, а затем инструкцию SELECT для одной и той же таблицы, измененные данные будут включены в результирующий набор.

    Примечание

    В режиме изоляции моментальных снимков данные FILESTREAM, считанные любой инструкцией транзакции, будут согласованы на уровне транзакции с версией данных, существовавших на момент начала транзакции, а не на момент начала выполнения инструкции.

    SERIALIZABLE
    Указывает следующее.

    • Инструкции не могут считывать данные, которые были изменены другими транзакциями, но еще не были зафиксированы.

      Другие транзакции не могут изменять данные, считываемые текущей транзакцией, до ее завершения.

      Другие транзакции не могут вставлять новые строки со значениями ключа, которые входят в диапазон ключей, считываемых инструкциями текущей транзакции, до ее завершения.

    Блокировка диапазона устанавливается в диапазоне значений ключа, соответствующих условиям поиска любой инструкции, выполненной во время транзакции. Обновление и вставка строк, удовлетворяющих инструкциям текущей транзакции, блокируется для других транзакций. Это гарантирует, что если какая-либо инструкция транзакции выполняется повторно, она будет считывать тот же самый набор строк. Блокировки диапазона сохраняются до завершения транзакции. Это самый строгий уровень изоляции, поскольку он блокирует целые диапазоны ключей и сохраняет блокировку до завершения транзакции. Из-за низкого параллелизма этот параметр рекомендуется использовать только при необходимости. Этот параметр действует так же, как и настройка HOLDLOCK всех таблиц во всех инструкциях SELECT в транзакции.

Замечания

Одновременно может быть установлен только один параметр уровня изоляции, который продолжает действовать для текущего соединения до тех пор, пока не будет явно изменен. Все операции считывания, выполняемые в рамках транзакции, функционируют в соответствии с правилами уровня изоляции, если только табличное указание в предложении FROM инструкции не задает другое поведение блокировки или управления версиями строк для таблицы.

Уровни изоляции транзакции определяют тип блокировки, применяемый к операциям считывания. Совмещаемые блокировки, применяемые для READ COMMITTED или REPEATABLE READ, как правило, являются блокировками строк, но при этом, если в процессе считывания идет обращение к большому числу строк, блокировка строк может быть расширена до блокировки страниц или таблиц. Если строка была изменена транзакцией после считывания, для защиты такой строки транзакция применяет монопольную блокировку, которая сохраняется до завершения транзакции. Например, если транзакция REPEATABLE READ имеет разделяемую блокировку строки и при этом изменяет ее, совмещаемая блокировка преобразуется в монопольную.

В любой момент транзакции можно переключиться с одного уровня изоляции на другой, однако есть одно исключение. Это смена уровня изоляции на уровень изоляции SNAPSHOT. Такая смена приводит к ошибке и откату транзакции. Однако для транзакции, которая была начата с уровнем изоляции SNAPSHOT, можно установить любой другой уровень изоляции.

Когда для транзакции изменяется уровень изоляции, ресурсы, которые считываются после изменения, защищаются в соответствии с правилами нового уровня. Ресурсы, которые считываются до изменения, остаются защищенными в соответствии с правилами предыдущего уровня. Например, если для транзакции уровень изоляции изменяется с READ COMMITTED на SERIALIZABLE, то совмещаемые блокировки, полученные после изменения, будут удерживаться до завершения транзакции.

Если инструкция SET TRANSACTION ISOLATION LEVEL использовалась в хранимой процедуре или триггере, то при возврате управления из них уровень изоляции будет изменен на тот, который действовал на момент их вызова. Например, если уровень изоляции REPEATABLE READ устанавливается в пакете, а пакет затем вызывает хранимую процедуру, которая меняет уровень изоляции на SERIALIZABLE, при возвращении хранимой процедурой управления пакету, настройки уровня изоляции меняются назад на REPEATABLE READ.

Примечание

Определяемые пользователем функции и типы данных среды CLR не могут выполнять инструкцию SET TRANSACTION ISOLATION LEVEL.Однако уровень изоляции можно переопределить с помощью табличного указания.Дополнительные сведения см. в разделе Табличные указания (Transact-SQL) .

Если для привязки двух сеансов используется процедура sp_bindsession, каждый сеанс сохраняет свои настройки уровня изоляции. Применение инструкции SET TRANSACTION ISOLATION LEVEL для изменения настройки уровня изоляции одного сеанса не повлияет на настройки других сеансов, привязанных к нему.

Инструкция SET TRANSACTION ISOLATION LEVEL работает во время выполнения, но не во время синтаксического анализа.

Оптимизированные операции массовой загрузки, работающие с кучами, блокируют запросы, которые выполняются со следующими уровнями изоляции:

    READ UNCOMMITTED

    READ COMMITTED с использованием управления версиями строк

Обратное также верно - запросы, которые выполняются с этими уровнями изоляции, блокируют оптимизированные операции массовой загрузки, работающие с кучами. Дополнительные сведения об операциях массовой загрузки см. в разделе .

Базы данных с поддержкой FILESTREAM поддерживают следующие уровни изоляции транзакций.

Примеры

В следующем примере устанавливается уровень изоляции TRANSACTION ISOLATION LEVEL для сеанса. Для каждой последующей инструкции Transact-SQLSQL Server сохраняет все совмещаемые блокировки до конца транзакции.

USE AdventureWorks2012; GO SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; GO BEGIN TRANSACTION; GO SELECT * FROM HumanResources.EmployeePayHistory; GO SELECT * FROM HumanResources.Department; GO COMMIT TRANSACTION; GO

Уровень изолированности транзакций - значение, определяющее уровень, при котором в транзакции допускаются несогласованные данные, то есть степень изолированности одной транзакции от другой. Более высокий уровень изолированности повышает точность данных, но при этом может снижаться количество параллельно выполняемых транзакций. С другой стороны, более низкий уровень изолированности позволяет выполнять больше параллельных транзакций, но снижает точность данных.

Проблемы параллельного доступа с использованием транзакций

Транзакция 1 Транзакция 2
UPDATE tbl1 SET f2=f2+1 WHERE f1=1;
COMMIT;
SELECT f2 FROM tbl1 WHERE f1=1;

В транзакции 2 выбирается значение поля f2, затем в транзакции 1 изменяется значение поля f2. При повторной попытке выбора значения из поля f2 в транзакции 2 будет получен другой результат. Эта ситуация особенно неприемлема, когда данные считываются с целью их частичного изменения и обратной записи в базу данных.

Фантомное чтение

Ситуация, когда при повторном чтении в рамках одной транзакции одна и та же выборка дает разные множества строк.

Предположим, имеется две транзакции, открытые различными приложениями, в которых выполнены следующие SQL-операторы:

Транзакция 1 Транзакция 2
SELECT SUM(f2) FROM tbl1;
INSERT INTO tbl1 (f1,f2) VALUES (15,20);
COMMIT;
SELECT SUM(f2) FROM tbl1;

В транзакции 2 выполняется SQL-оператор, использующий все значения поля f2. Затем в транзакции 1 выполняется вставка новой строки, приводящая к тому, что повторное выполнение SQL-оператора в транзакции 2 выдаст другой результат. Такая ситуация называется фантомным чтением. От неповторяющегося чтения оно отличается тем, что результат повторного обращения к данным изменился не из-за изменения/удаления самих этих данных, а из-за появления новых (фантомных) данных.

Уровни изоляции

Под «уровнем изоляции транзакций » понимается степень обеспечиваемой внутренними механизмами СУБД (то есть не требующей специального программирования) защиты от всех или некоторых видов вышеперечисленных несогласованностей данных, возникающих при параллельном выполнении транзакций. Стандарт SQL-92 определяет шкалу из четырёх уровней изоляции: Read uncommitted, Read committed, Repeatable read, Serializable. Первый из них является самым слабым, последний - самым сильным, каждый последующий включает в себя все предыдущие.

Read uncommitted (чтение незафиксированных данных)

Низший (первый) уровень изоляции. Он гарантирует только отсутствие потерянных обновлений . Если несколько параллельных транзакций пытаются изменять одну и ту же строку таблицы, то в окончательном варианте строка будет иметь значение, определенное всем набором успешно выполненных транзакций. При этом возможно считывание не только логически несогласованных данных, но и данных, изменения которых ещё не зафиксированы.

Типичный способ реализации данного уровня изоляции - блокировка данных на время выполнения команды изменения, что гарантирует, что команды изменения одних и тех же строк, запущенные параллельно, фактически выполнятся последовательно, и ни одно из изменений не потеряется. Транзакции, выполняющие только чтение, при данном уровне изоляции никогда не блокируются.

Read committed (чтение фиксированных данных)

Большинство промышленных СУБД, в частности, Microsoft SQL Server , PostgreSQL и Oracle , по умолчанию используют именно этот уровень. На этом уровне обеспечивается защита от чернового, «грязного» чтения, тем не менее, в процессе работы одной транзакции другая может быть успешно завершена и сделанные ею изменения зафиксированы. В итоге первая транзакция будет работать с другим набором данных.

Реализация завершённого чтения может основываться на одном из двух подходов: блокировании или версионности.

Блокирование читаемых и изменяемых данных. Заключается в том, что читающая транзакция блокирует читаемые данные в разделяемом (shared) режиме, в результате чего параллельная транзакция, пытающаяся изменить эти данные, приостанавливается, а пишущая транзакция блокирует изменяемые данные для читающих транзакций, работающих на уровне read committed или более высоком, до своего завершения, препятствуя, таким образом, «грязному» чтению. Сохранение нескольких версий параллельно изменяемых строк. При каждом изменении строки СУБД создаёт новую версию этой строки, с которой продолжает работать изменившая данные транзакция, в то время как любой другой «читающей» транзакции возвращается последняя зафиксированная версия. Преимущество такого подхода в том, что он обеспечивает бо́льшую скорость, так как предотвращает блокировки. Однако он требует, по сравнению с первым, существенно бо́льшего расхода оперативной памяти, которая тратится на хранение версий строк. Кроме того, при параллельном изменении данных несколькими транзакциями может создаться ситуация, когда несколько параллельных транзакций произведут несогласованные изменения одних и тех же данных (поскольку блокировки отсутствуют, ничто не помешает это сделать). Тогда та транзакция, которая зафиксируется первой, сохранит свои изменения в основной БД, а остальные параллельные транзакции окажется невозможно зафиксировать (так как это приведёт к потере обновления первой транзакции). Единственное, что может в такой ситуации СУБД - это откатить остальные транзакции и выдать сообщение об ошибке «Запись уже изменена».

Конкретный способ реализации выбирается разработчиками СУБД, а в ряде случае может настраиваться. Так, по умолчанию MS SQL использует блокировки, но (в версии 2005 и выше) при установке параметра READ_COMMITTED_SNAPSHOT базы данных переходит на стратегию версионности, Oracle исходно работает только по версионной схеме. В Informix можно предотвратить конфликты между читающими и пишущими транзакциями, установив параметр конфигурации USELASTCOMMITTED (начиная с версии 11.1), при этом читающая транзакция будет получать последние подтвержденные данные

Repeatable read (повторяемость чтения)

Уровень, при котором читающая транзакция «не видит» изменения данных, которые были ею ранее прочитаны. При этом никакая другая транзакция не может изменять данные, читаемые текущей транзакцией, пока та не окончена.

Блокировки в разделяющем режиме применяются ко всем данным, считываемым любой инструкцией транзакции, и сохраняются до её завершения. Это запрещает другим транзакциям изменять строки, которые были считаны незавершённой транзакцией. Однако другие транзакции могут вставлять новые строки, соответствующие условиям поиска инструкций, содержащихся в текущей транзакции. При повторном запуске инструкции текущей транзакцией будут извлечены новые строки, что приведёт к фантомному чтению. Учитывая то, что разделяющие блокировки сохраняются до завершения транзакции, а не снимаются в конце каждой инструкции, степень параллелизма ниже, чем при уровне изоляции READ COMMITTED. Поэтому пользоваться данным и более высокими уровнями транзакций без необходимости обычно не рекомендуется.

Serializable (упорядочиваемость)

Самый высокий уровень изолированности; транзакции полностью изолируются друг от друга, каждая выполняется так, как будто параллельных транзакций не существует. Только на этом уровне параллельные транзакции не подвержены эффекту «фантомного чтения».

Поддержка изоляции транзакций в реальных СУБД

СУБД, обеспечивающие транзакционность, не всегда поддерживают все четыре уровня, а также могут вводить дополнительные. Возможны также различные нюансы в обеспечении изоляции.

Так, Oracle в принципе не поддерживает нулевой уровень, так как его реализация транзакций исключает «грязные чтения», и формально не позволяет устанавливать уровень Repeatable read, то есть поддерживает только Read committed (по умолчанию) и Serializable. При этом на уровне отдельных команд он, фактически, гарантирует повторяемость чтения (если команда SELECT в первой транзакции выбирает из базы набор строк, и в это время параллельная вторая транзакция изменяет какие-то из этих строк, то результирующий набор, полученный первой транзакцией, будет содержать неизменённые строки, как будто второй транзакции не было). Также Oracle поддерживает так называемые READ-ONLY транзакции, которые соответствуют Serializable, но при этом не могут сами изменять данные.

Microsoft SQL Server поддерживает все четыре стандартных уровня изоляции транзакций, а дополнительно - уровень SNAPSHOT, находящийся между Repeatable read и Serialized. Транзакция, работающая на данном уровне, видит только те изменения данных, которые были зафиксированы до её запуска, а также изменения, внесённые ею самой, то есть ведёт себя так, как будто получила при запуске моментальный снимок данных БД и работает с ним.

Поведение при различных уровнях изолированности

«+» - предотвращает, «-» - не предотвращает.

Напишите отзыв о статье "Уровень изолированности транзакций"

Примечания

Отрывок, характеризующий Уровень изолированности транзакций

– Андрея Николаевича? мы мимо проедем, я вас проведу к нему.
– Что ж левый фланг? – спросил Пьер.
– По правде вам сказать, entre nous, [между нами,] левый фланг наш бог знает в каком положении, – сказал Борис, доверчиво понижая голос, – граф Бенигсен совсем не то предполагал. Он предполагал укрепить вон тот курган, совсем не так… но, – Борис пожал плечами. – Светлейший не захотел, или ему наговорили. Ведь… – И Борис не договорил, потому что в это время к Пьеру подошел Кайсаров, адъютант Кутузова. – А! Паисий Сергеич, – сказал Борис, с свободной улыбкой обращаясь к Кайсарову, – А я вот стараюсь объяснить графу позицию. Удивительно, как мог светлейший так верно угадать замыслы французов!
– Вы про левый фланг? – сказал Кайсаров.
– Да, да, именно. Левый фланг наш теперь очень, очень силен.
Несмотря на то, что Кутузов выгонял всех лишних из штаба, Борис после перемен, произведенных Кутузовым, сумел удержаться при главной квартире. Борис пристроился к графу Бенигсену. Граф Бенигсен, как и все люди, при которых находился Борис, считал молодого князя Друбецкого неоцененным человеком.
В начальствовании армией были две резкие, определенные партии: партия Кутузова и партия Бенигсена, начальника штаба. Борис находился при этой последней партии, и никто так, как он, не умел, воздавая раболепное уважение Кутузову, давать чувствовать, что старик плох и что все дело ведется Бенигсеном. Теперь наступила решительная минута сражения, которая должна была или уничтожить Кутузова и передать власть Бенигсену, или, ежели бы даже Кутузов выиграл сражение, дать почувствовать, что все сделано Бенигсеном. Во всяком случае, за завтрашний день должны были быть розданы большие награды и выдвинуты вперед новые люди. И вследствие этого Борис находился в раздраженном оживлении весь этот день.
За Кайсаровым к Пьеру еще подошли другие из его знакомых, и он не успевал отвечать на расспросы о Москве, которыми они засыпали его, и не успевал выслушивать рассказов, которые ему делали. На всех лицах выражались оживление и тревога. Но Пьеру казалось, что причина возбуждения, выражавшегося на некоторых из этих лиц, лежала больше в вопросах личного успеха, и у него не выходило из головы то другое выражение возбуждения, которое он видел на других лицах и которое говорило о вопросах не личных, а общих, вопросах жизни и смерти. Кутузов заметил фигуру Пьера и группу, собравшуюся около него.
– Позовите его ко мне, – сказал Кутузов. Адъютант передал желание светлейшего, и Пьер направился к скамейке. Но еще прежде него к Кутузову подошел рядовой ополченец. Это был Долохов.
– Этот как тут? – спросил Пьер.
– Это такая бестия, везде пролезет! – отвечали Пьеру. – Ведь он разжалован. Теперь ему выскочить надо. Какие то проекты подавал и в цепь неприятельскую ночью лазил… но молодец!..
Пьер, сняв шляпу, почтительно наклонился перед Кутузовым.
– Я решил, что, ежели я доложу вашей светлости, вы можете прогнать меня или сказать, что вам известно то, что я докладываю, и тогда меня не убудет… – говорил Долохов.
– Так, так.
– А ежели я прав, то я принесу пользу отечеству, для которого я готов умереть.
– Так… так…
– И ежели вашей светлости понадобится человек, который бы не жалел своей шкуры, то извольте вспомнить обо мне… Может быть, я пригожусь вашей светлости.
– Так… так… – повторил Кутузов, смеющимся, суживающимся глазом глядя на Пьера.
В это время Борис, с своей придворной ловкостью, выдвинулся рядом с Пьером в близость начальства и с самым естественным видом и не громко, как бы продолжая начатый разговор, сказал Пьеру:
– Ополченцы – те прямо надели чистые, белые рубахи, чтобы приготовиться к смерти. Какое геройство, граф!
Борис сказал это Пьеру, очевидно, для того, чтобы быть услышанным светлейшим. Он знал, что Кутузов обратит внимание на эти слова, и действительно светлейший обратился к нему:
– Ты что говоришь про ополченье? – сказал он Борису.
– Они, ваша светлость, готовясь к завтрашнему дню, к смерти, надели белые рубахи.
– А!.. Чудесный, бесподобный народ! – сказал Кутузов и, закрыв глаза, покачал головой. – Бесподобный народ! – повторил он со вздохом.
– Хотите пороху понюхать? – сказал он Пьеру. – Да, приятный запах. Имею честь быть обожателем супруги вашей, здорова она? Мой привал к вашим услугам. – И, как это часто бывает с старыми людьми, Кутузов стал рассеянно оглядываться, как будто забыв все, что ему нужно было сказать или сделать.
Очевидно, вспомнив то, что он искал, он подманил к себе Андрея Сергеича Кайсарова, брата своего адъютанта.
– Как, как, как стихи то Марина, как стихи, как? Что на Геракова написал: «Будешь в корпусе учитель… Скажи, скажи, – заговорил Кутузов, очевидно, собираясь посмеяться. Кайсаров прочел… Кутузов, улыбаясь, кивал головой в такт стихов.
Когда Пьер отошел от Кутузова, Долохов, подвинувшись к нему, взял его за руку.
– Очень рад встретить вас здесь, граф, – сказал он ему громко и не стесняясь присутствием посторонних, с особенной решительностью и торжественностью. – Накануне дня, в который бог знает кому из нас суждено остаться в живых, я рад случаю сказать вам, что я жалею о тех недоразумениях, которые были между нами, и желал бы, чтобы вы не имели против меня ничего. Прошу вас простить меня.
Пьер, улыбаясь, глядел на Долохова, не зная, что сказать ему. Долохов со слезами, выступившими ему на глаза, обнял и поцеловал Пьера.
Борис что то сказал своему генералу, и граф Бенигсен обратился к Пьеру и предложил ехать с собою вместе по линии.
– Вам это будет интересно, – сказал он.
– Да, очень интересно, – сказал Пьер.
Через полчаса Кутузов уехал в Татаринову, и Бенигсен со свитой, в числе которой был и Пьер, поехал по линии.

Бенигсен от Горок спустился по большой дороге к мосту, на который Пьеру указывал офицер с кургана как на центр позиции и у которого на берегу лежали ряды скошенной, пахнувшей сеном травы. Через мост они проехали в село Бородино, оттуда повернули влево и мимо огромного количества войск и пушек выехали к высокому кургану, на котором копали землю ополченцы. Это был редут, еще не имевший названия, потом получивший название редута Раевского, или курганной батареи.
Пьер не обратил особенного внимания на этот редут. Он не знал, что это место будет для него памятнее всех мест Бородинского поля. Потом они поехали через овраг к Семеновскому, в котором солдаты растаскивали последние бревна изб и овинов. Потом под гору и на гору они проехали вперед через поломанную, выбитую, как градом, рожь, по вновь проложенной артиллерией по колчам пашни дороге на флеши [род укрепления. (Примеч. Л.Н. Толстого.) ], тоже тогда еще копаемые.
Бенигсен остановился на флешах и стал смотреть вперед на (бывший еще вчера нашим) Шевардинский редут, на котором виднелось несколько всадников. Офицеры говорили, что там был Наполеон или Мюрат. И все жадно смотрели на эту кучку всадников. Пьер тоже смотрел туда, стараясь угадать, который из этих чуть видневшихся людей был Наполеон. Наконец всадники съехали с кургана и скрылись.
Бенигсен обратился к подошедшему к нему генералу и стал пояснять все положение наших войск. Пьер слушал слова Бенигсена, напрягая все свои умственные силы к тому, чтоб понять сущность предстоящего сражения, но с огорчением чувствовал, что умственные способности его для этого были недостаточны. Он ничего не понимал. Бенигсен перестал говорить, и заметив фигуру прислушивавшегося Пьера, сказал вдруг, обращаясь к нему:
– Вам, я думаю, неинтересно?
– Ах, напротив, очень интересно, – повторил Пьер не совсем правдиво.
С флеш они поехали еще левее дорогою, вьющеюся по частому, невысокому березовому лесу. В середине этого
леса выскочил перед ними на дорогу коричневый с белыми ногами заяц и, испуганный топотом большого количества лошадей, так растерялся, что долго прыгал по дороге впереди их, возбуждая общее внимание и смех, и, только когда в несколько голосов крикнули на него, бросился в сторону и скрылся в чаще. Проехав версты две по лесу, они выехали на поляну, на которой стояли войска корпуса Тучкова, долженствовавшего защищать левый фланг.
Здесь, на крайнем левом фланге, Бенигсен много и горячо говорил и сделал, как казалось Пьеру, важное в военном отношении распоряжение. Впереди расположения войск Тучкова находилось возвышение. Это возвышение не было занято войсками. Бенигсен громко критиковал эту ошибку, говоря, что было безумно оставить незанятою командующую местностью высоту и поставить войска под нею. Некоторые генералы выражали то же мнение. Один в особенности с воинской горячностью говорил о том, что их поставили тут на убой. Бенигсен приказал своим именем передвинуть войска на высоту.
Распоряжение это на левом фланге еще более заставило Пьера усумниться в его способности понять военное дело. Слушая Бенигсена и генералов, осуждавших положение войск под горою, Пьер вполне понимал их и разделял их мнение; но именно вследствие этого он не мог понять, каким образом мог тот, кто поставил их тут под горою, сделать такую очевидную и грубую ошибку.
Пьер не знал того, что войска эти были поставлены не для защиты позиции, как думал Бенигсен, а были поставлены в скрытое место для засады, то есть для того, чтобы быть незамеченными и вдруг ударить на подвигавшегося неприятеля. Бенигсен не знал этого и передвинул войска вперед по особенным соображениям, не сказав об этом главнокомандующему.

Князь Андрей в этот ясный августовский вечер 25 го числа лежал, облокотившись на руку, в разломанном сарае деревни Князькова, на краю расположения своего полка. В отверстие сломанной стены он смотрел на шедшую вдоль по забору полосу тридцатилетних берез с обрубленными нижними сучьями, на пашню с разбитыми на ней копнами овса и на кустарник, по которому виднелись дымы костров – солдатских кухонь.
Как ни тесна и никому не нужна и ни тяжка теперь казалась князю Андрею его жизнь, он так же, как и семь лет тому назад в Аустерлице накануне сражения, чувствовал себя взволнованным и раздраженным.
Приказания на завтрашнее сражение были отданы и получены им. Делать ему было больше нечего. Но мысли самые простые, ясные и потому страшные мысли не оставляли его в покое. Он знал, что завтрашнее сражение должно было быть самое страшное изо всех тех, в которых он участвовал, и возможность смерти в первый раз в его жизни, без всякого отношения к житейскому, без соображений о том, как она подействует на других, а только по отношению к нему самому, к его душе, с живостью, почти с достоверностью, просто и ужасно, представилась ему. И с высоты этого представления все, что прежде мучило и занимало его, вдруг осветилось холодным белым светом, без теней, без перспективы, без различия очертаний. Вся жизнь представилась ему волшебным фонарем, в который он долго смотрел сквозь стекло и при искусственном освещении. Теперь он увидал вдруг, без стекла, при ярком дневном свете, эти дурно намалеванные картины. «Да, да, вот они те волновавшие и восхищавшие и мучившие меня ложные образы, – говорил он себе, перебирая в своем воображении главные картины своего волшебного фонаря жизни, глядя теперь на них при этом холодном белом свете дня – ясной мысли о смерти. – Вот они, эти грубо намалеванные фигуры, которые представлялись чем то прекрасным и таинственным. Слава, общественное благо, любовь к женщине, самое отечество – как велики казались мне эти картины, какого глубокого смысла казались они исполненными! И все это так просто, бледно и грубо при холодном белом свете того утра, которое, я чувствую, поднимается для меня». Три главные горя его жизни в особенности останавливали его внимание. Его любовь к женщине, смерть его отца и французское нашествие, захватившее половину России. «Любовь!.. Эта девочка, мне казавшаяся преисполненною таинственных сил. Как же я любил ее! я делал поэтические планы о любви, о счастии с нею. О милый мальчик! – с злостью вслух проговорил он. – Как же! я верил в какую то идеальную любовь, которая должна была мне сохранить ее верность за целый год моего отсутствия! Как нежный голубок басни, она должна была зачахнуть в разлуке со мной. А все это гораздо проще… Все это ужасно просто, гадко!