Проблемы взаимодействия транзакций
Проблемы взаимодействия транзакций
Проблемы, связанные с одновременным доступом, возникают даже в относительно простых приложениях. Представьте, например, такой случай. Вы пишете приложение, которое предназначено для обработки заказов и включает в себя четыре таблицы: ORDER_MASTER (главная таблица заказов), CUSTOMER (таблица клиентов), LINE_ITEM (строка заказа) и INVENTORY (таблица с описанием товаров). Выполняются следующие условия.
- В таблице ORDERJVlASTER первичным ключом является поле OrderNumber (номер заказа), а поле CustomerNumber (номер клиента) — внешним ключом, который ссылается на таблицу CUSTOMER.
- В таблице LINEJTEM первичным ключом является поле LineNumber (номер строки), а поле ItemNumber (идентификационный номер товара) — внешним ключом, который ссылается на таблицу INVENTORY, и, наконец, одним из ее полей является Quantity (количество).
- В таблице INVENTORY первичным ключом является поле ItemNumber; кроме того, в ней есть поле QuantityOnHand (количество в наличии).
- Во всех трех таблицах есть еще и другие столбцы, но они в этом примере не рассматриваются.
Политика вашей компании состоит в том, чтобы каждый заказ или выполнять полностью, или не выполнять вовсе. Частичные выполнения заказов не допускаются. (Спокойно. Это же воображаемая ситуация.) Вы пишете приложение ORDER_PROCESSING (обработка заказа), которое должно обрабатывать в таблице ORDER_MASTER каждый новый заказ, причем делать это следующим образом. Приложение вначале определяет, возможна ли отгрузка всех заказанных товаров. Если да, то приложение оформляет заказ, соответственно уменьшая в таблице INVENTORY значение столбца QuantityOnHand и удаляя из таблиц ORDER JvlASTER и LINE_ITEM записи, относящиеся к этому заказу. Пока все хорошо. Ваше приложение должно обрабатывать заказы одним из двух способов.
- Первый способ состоит в том, чтобы в таблице INVENTORY обрабатывалась запись, которая соответствует каждой записи таблицы LINEJTEM. Если значение в QuantityOnHand является достаточно большим, то приложение его уменьшит. Но если это значение меньше требуемого, выполняется откат транзакции. Это делается для того, чтобы можно было восстановить все изменения, уже внесенные в таблицу INVENTORY в результате обработки предыдущих строк таблицы LINEJTEM этого заказа.
- Второй способ состоит в том, что проверяется каждая запись таблицы INVENTORY, соответствующая какой-либо записи заказа, находящейся в таблице LINEJTEM. Если значения во всех этих записях таблицы INVENTORY достаточно большие, тогда выполняется их уменьшение.
Если заказ выполним, большей эффективностью обладает первый способ, если же нет — второй. Таким образом, если большая часть заказов выполнима, необходимо использовать первый способ. В противном случае больше подойдет второй. Предположим, что это приложение установлено и запущено в многопользовательской системе, в которой нет достаточного управления одновременным доступом. Сразу же возникают следующие проблемы.
- Пользователь 1 запускает обработку заказа с помощью первого способа. На складе находится 10 единиц товара 1, и все это количество требуется для выполнения заказа. После выполнения заказа количество товара 1 становится равным нулю. Вот тут-то и начинается самое интересное. Пользователь 2 запускает обработку небольшого заказа на одну единицу товара 1 и обнаруживает, что заказ оформить нельзя, так как на складе нет нужного количества этого товара. А так как заказ оформить нельзя, выполняется откат. В это время пользователь 1 еще пытается заказать пять единиц товара 37, но на складе их всего четыре. Поэтому и для заказа пользователя 1 также выполняется откат — этот заказ нельзя полностью оформить. И таблица
INVENTORY возвращается в состояние, в котором она была перед тем, как эти пользователи начали работать. Получается, что не оформлен ни один из заказов, хотя заказ пользователя 2 вполне можно было выполнить. - Второй способ не лучше, хотя и по другим причинам. Пользователь 1 может проверить все заказываемые товары и решить, что все они имеются. Но если в дело вмешается пользователь 2 и обработку заказа на один из этих товаров запустит до того, как пользователь 1 выполнит операцию уменьшения, то транзакция пользователя 1 может окончиться неудачно.