пятница, 22 января 2010 г.

Очистка сессии, уровни изоляции транзакций, выбор и установка уровня изоляции транзакций в Hibernate (Ч2)

это перевод книги Hibernate In Action Главы 5
Hibernate транзакции, параллельность и кэширование. Основы транзакций. (Ч1)

5.1.3 Очистка Session
Hibernate интерфейс Session реализует прозрачную запись. Изменения в модели предметной области, сделанные в области действия Session не распространяются немедленно на БД. Это позволяет Hibernate объединять множество изменений в минимальном количестве запросов к БД, помогая свести к минимуму воздействие задержек сети.


Например, если одно свойство объекта изменилось дважды в одной Transaction, Hibernate нужно выполнить только один SQL UPDATE. Другим примером полезности прозрачной записи, является то, что Hibernate может воспользоваться преимуществами пакетного(batch) выполнения запросов JDBC, когда выполняет несколько UPDATE, INSERT или DELETE выражений.
Очистка Hibernate сессии происходит только в следующих случаях:
•    когда Transaction фиксируется(коммит);
•    иногда перед выполнением запроса;
•    когда приложение вызывает Session.flush() явно;


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


    Вы можете контролировать это поведение явно, устанавливая Hibernate FlushMode через вызов Session.setFlushMode(). Режимами очистки сессии являются:
•    FlushMode.AUTO – по умолчанию. Устанавливает поведение, описанное только что.
•    FlushMode.COMMIT – указывает, что сессия не будет очищена перед выполнением запроса (она будет очищена только в конце транзакции БД). Имейте в виду, что этот параметр может затронуть устаревшие данные: изменения, внесенные в объекты только в памяти, могут вступать в противоречие с результатами запроса.
•    FlushMode.NEVER – позволяет вам самим указать, что только явные вызовы flush()  приведут к синхронизации состояния сессии с БД.
 

Мы не рекомендуем вам изменять эту настройку по умолчанию. Это позволяет организовать оптимизацию производительности в редких случаях. Аналогичным образом, большинству приложений редко нужно указывать flush() явно. Эта функция полезна, когда вы работаете с триггерами, смешивая Hibernate с прямым JDBC, или работает с ошибками JDBC драйверов. Вы должны быть осведомлены о такой возможности, но необязательно использовать её.
Теперь вы понимаете, что основой использования транзакций с Hibernate является интерфейс Transaction, давайте обратим наше внимание на одновременный доступ к данным.

Может показаться, как будто вы не должны заботиться об изоляции транзакции – термин подразумевает, что что-то является и не является изолированным. Это вводит в заблуждение. Полная изоляция одновременных операций является исключительно дорогостоящей операцией, с точки зрения масштабируемости приложений, поэтому БД предоставляет несколько степеней изоляции. Для большинства приложений, частичная изоляция транзакций является приемлемым вариантом. Важно понимать, что степень изоляции следует выбирать для приложения, которое использует Hibernate, а также понимать, как Hibernate интегрируется с возможностями транзакций БД.
 

5.1.4 Понимание уровней изоляции
БД (и другие транзакционные системы) пытаются обеспечить изоляцию транзакций, а это означает, что, с точки зрения каждой одновременной транзакции, никакие другие транзакции не выполняются.
    Традиционно, это реализуется с использованием блокировок. Транзакция может установить блокировки на конкретный элемент данных, временно предотвращая доступ к этому элементу, другим транзакциям. Некоторые современные БД, такие как Oracle и PostgreSQL реализуют изоляцию транзакций, используя многоверсионный контрольно-измерительный параллелизм, который обычно считается более масштабируемым решением. Мы обсудим изоляцию, предполагая модель блокировок (большинство из наших наблюдений также применимы к многоверсионному параллелизму).
    Эта дискуссия о транзакциях БД и об уровне изоляции, предоставляемому БД.  Hibernate не добавляет дополнительную семантику; он использует  все, что доступно с данной БД. Если вы следили за многолетним опытом, которые поставщики БД имели, реализуя контроль параллельности, то вы будете четко видеть преимущества этого подхода. Ваша задача, как разработчика Hibernate приложений, состоит в том, чтобы понять возможности вашей СУБД и как можно изменить поведение  изоляцию БД, в случае необходимости, конкретно в вашем сценарии (и по вашим требованиям целостности данных).

Вопросы изоляции
Во-первых, давайте взглянем на некоторые явления, которые нарушают полную изоляцию транзакций. Стандарт ANSI SQL определяет стандартные уровни изоляции транзакций, с точки зрения того, какие из этих явлений являются допустимыми:
•    Потерянное обновление – две транзакции обновляют строку, а затем вторая прерывает транзакцию, в результате чего оба изменения будут потеряны. Это происходит в системах, которые не реализуют какую-либо блокировку. Одновременные транзакции не изолированы.
•    Грязное чтение - одна транзакция читает изменения, сделанные в другой транзакции, которая ещё не до конца совершена. Это очень опасно, потому что эти изменения не зафиксированы.
•    Неповторяемое чтение – транзакция читает строку дважды и считывает различные состояния каждый раз. Например, другая транзакция может быть записана в строку и зафиксирована между двумя считываниями.
•    Вторая проблема обновлений – частный случай неповторяемого чтения. Представьте себе, что есть две параллельные транзакции, обе читают строки, одна записывает туда и фиксируется, и далее вторая пишет туда же и фиксируется. Изменения, сделанные первой транзакцией, будут потеряны.
•    Фантомное чтение – транзакция выполняет запрос дважды, и второй результирующий набор включает в себя строки, которые были не видны в первом результирующем наборе (это не обязательно должен быть точно такой же запрос). Такая ситуация вызвана другой транзакцией, которая вставляла новые строки между выполнением этих двух запросов.

Теперь, когда вы понимаете все плохое, что может произойти, мы можем определить различные уровни изоляции транзакций и увидеть, какие проблемы они предотвращают.
Уровни изоляции
Стандартные уровни изоляции определяются стандартом ANSI SQL, но без привязки к SQL БД. JTA определяет те же уровни изоляции, и вы будете использовать эти уровни, чтобы позднее заявить желаемые уровень изоляции транзакции:
•    Неподтверждённое чтение – разрешает грязные чтения, но без потери обновлений. Одна транзакция может не писать в строку, если другая незафиксированная транзакция уже записывает туда. Однако, любая транзакция может читать любые строки. Этот уровень изоляции может быть реализован с использованием эксклюзивной блокировки записи.
•    Подтверждённое чтение  - разрешает неповторяемые чтения, но не грязные чтения. Это может быть достигнуто с помощью мгновенных общих блокировок чтения и эксклюзивной блокировки записи. Однако, незафиксированные пишущие транзакции блокируют  все другие транзакции на доступ к строке.
•    Повторяемое чтение – не допускает ни неповторяемого чтения, ни грязного чтения. Фантомное чтение может произойти. Это может быть достигнуто с использованием общих блокировок на чтение и эксклюзивной блокировки на запись. Блок чтения пишущих транзакций (но не другие операции чтения) и блок  пишущих транзакций блокируют все другие транзакции.
•    Упорядоченный (сериализуемый) – обеспечивает строгую изоляцию транзакций. Он эмулирует последовательное выполнение операций, как если бы операция была выполнена одна за другой последовательно, а не параллельно. Упорядоченность не может быть реализована с использованием только блокировки на уровне строк; это должен быть другой механизм, который предотвращает становление видимой, только что вставленной строки, из транзакции, которая уже выполняет запрос, возвращающий строку.
Приятно знать, как определены все эти технические термины, но как это поможет вам выбрать уровень изоляции, необходимый для вашего приложения?

5.1.5 Выбор уровня изоляции
Разработчики (включая нас) зачастую не уверены в уровне изоляции транзакций для использования в работающих приложениях. Слишком большая степень изоляции повредит производительность приложений с большим количеством одновременных операций. Недостаточная изоляция может привести к тонким ошибкам в нашем приложении, которые не могут быть воспроизведены, и мы никогда не узнаем об их наличии, пока система не поработает под большой нагрузкой.
    Обратите внимание на то, что мы называем кэшированием и оптимистической блокировкой (с помощью версионирования), эти две концепции объясняются позже в этой главе. Вы можете пропустить этот раздел и вернуться, когда придет время принимать решение по уровню изоляции в вашем приложении. Выбор правильного уровня изоляции, в конце концов, в значительной степени зависит от конкретного сценария. Это обсуждение содержит рекомендации; это не является прописными истинами.
    Hibernate старается быть как можно более прозрачным в отношении семантики транзакций БД. Тем не менее, кэширование и оптимистические блокировки влияют на эти семантики. Итак, что же представляет собой разумный уровень изоляции БД в Hibernate приложении?
    Во-первых, устраните уровень чтения неподтвержденного. Крайне опасно использовать изменения, незафиксированные одной транзакцией, в других.  Откат или сбой одной транзакции повлияет на другие параллельные транзакции. Откат первой транзакции может сбить другие транзакции или, возможно, даже оставить БД в противоречивом состоянии. Вполне возможно, что изменения, внесенные транзакцией, которая заканчивается во время отката, могут быть зафиксированы в любом случае, поскольку они могут быть считаны и затем распространятся  на другую транзакцию, которая была успешна!
    Во-вторых, большинству приложений не нужна упорядоченная (сериализованная)  изоляция (фантомное чтение обычно не проблема), и  этот уровень изоляции, как правило, плохо масштабируется. Немногие из существующих приложений используют упорядоченную изоляцию в работающем приложении; а скорее они используют пессимистическую блокировку (см. раздел 5.1.7, «Использование пессимистических блокировок»),  которая эффективно справляется с упорядоченным выполнением   операций в определенных ситуациях.
    Это оставляет вам выбор между чтением подтвержденного  и повторяемого чтения. Этот уровень изоляции исключает вероятности того, что одна транзакция может перезаписать изменения, сделанные другой параллельной транзакцией (вторая проблема  потери обновлений), если доступ к данным осуществляется в одной атомарной транзакции БД. Это важный вопрос, но использование повторяемого чтения не является единственным путем к его решению.
    Предположим, что вы используете версионирование данных, то, что Hibernate может сделать для вас автоматически. Сочетание (обязательного) сессионного Hibernate-кэша первого уровня и версионирования уже дают вам большинство функций для изоляции повторяемого чтения. В частности, версионирование предотвращает вторую проблему потери обновлений, и сессионный кэш первого уровня гарантирует, что состояние хранимых сущностей, загружаемых одной транзакцией, изолированы от изменений, делаемых в других транзакциях. Таким образом, изоляция чтения зафиксированного для всех транзакций БД было бы приемлемо, если вы используете версионирование данных.
    Повторяемое чтение обеспечивает немного большую воспроизводимость для результатов запроса (только на время транзакции БД), но поскольку фантомное считывание все ещё возможно, существует не так много смысла в этом (запрашивать одну таблицу дважды в одной транзакции  также не характерно для веб-приложений).
    Вы также можете рассмотреть (по желанию) Hibernate кэш второго уровня. Он может обеспечить такую же изоляцию транзакций, как и транзакция БД, но он может даже ослабить изоляцию. Если вы интенсивно используете стратегию параллельного кэширования для КЭШа второго уровня, который сохраняет семантику повторяемого чтения (например, для стратегии чтения-записи и особенно для стратегии нестрогого чтения-записи, обе они обсуждаются ниже в этой главе), выбор уровня изоляции по умолчанию прост: вы не можете достичь повторяемого чтения в любом случае, так что нет смысла в замедлении БД. С другой стороны, вы можете не использовать кэш второго уровня для критических классов, или вы можете полностью использовать транзакционный кэш, который обеспечивает изоляцию повторяемого чтения. Должны ли вы использовать повторяемое чтение в этом случае? Вы можете, если хотите, но это, наверное, не стоит накладных расходов.
    Установка уровня изоляции транзакций позволяет выбрать хорошую стратегию блокировки по умолчанию для всех ваших транзакций БД. Как установить уровень изоляции?

5.1.6 Установка уровня изоляции
Каждое JDBC соединение к БД использует уровень изоляции, установленный самой БД, как правило, это чтение подтвержденного или повторяемое чтение. Это значение по умолчанию может быть изменено в конфигурации БД. Вы также можете установить уровень изоляции транзакций для JDBC соединений, с помощью опции конфигурации Hibernate:

    hibernate.connection.isolation = 4

Hibernate затем установит этот уровень изоляции на каждое соединении JDBC, полученное из пула соединений, до начала транзакции. Разумные значения для этой опции выглядят следующим образом (вы также можете найти их, как константы в java.sql.Connection):
•    1 – изоляция уровня чтения неподтвержденного;
•    2 – изоляция уровня чтения подтвержденного;
•    4 – изоляция уровня повторяемого чтения;
•    8 – упорядоченная изоляция.

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

Пессимистическая и оптимистическая блокировки, транзакции приложения, версионирование, детализация сессии (Ч3) 
Кэширование в Hibernate (Ч4) 
Hibernate кэширование на практике и заключение (Ч5)

5 комментариев:

  1. Большое вам спасибо, за интересные статьи, очень давно уже хотел разобраться с hibernate и тут такой подарок!)

    ОтветитьУдалить
  2. Не за что! Общий объем статей составляет около 50 страниц. На данный момент выложена только треть материала. Статьи будут выкладываться каждый день.

    ОтветитьУдалить
  3. Супер! Спасибо, жду продолжения)

    ОтветитьУдалить
  4. Молодец, перевел :)

    ОтветитьУдалить
  5. Спасибо! С интересом читаю.

    Насчет этого:
    "...большинству приложений редко нужно указывать flush() явно. Эта функция полезна, когда вы работаете с триггерами, смешивая Hibernate с прямым JDBC, или работает с ошибками JDBC драйверов..."
    В случае с триггерами в БД или прямым UPDATE по JDBC flush не помогает увидеть сделанные изменения. Надо использовать evict.

    ОтветитьУдалить