Re[5]: Вопрос про одновременный доступ
От: Sinclair Россия https://github.com/evilguest/
Дата: 27.08.02 15:20
Оценка: 127 (18)
Здравствуйте Al-Ko, Вы писали:

AK>А скажите, поддерживает ли механизм транзакций такие возможности:


AK>представим себе две связанные таблицы (один-ко-многим), допустим, накладная — перечень товаров. Один юзер выбрал запись с накладной номер NN, считал через SELECT перечень находящихся в ней товаров и редактирует этот перечень.

AK>Второй юзер, не ведая того, что накладную NN выбрал первый, тоже считал ее и редактирует, хотя вроде как и не имеет на это право.

AK>Есть ли возможность сообщить юзеру №2, что, хотя он прочел накладную NN, он не имеет право ее редактировать? Есть ли возможность сообщить юзеру №2, кто в данный момент редактирует накладную NN из других пользователей?


AK>Или механизм транзакций может только сообщить юзеру №2 при сохранении накладной, что все изменения, которые он сделал, недействительны, так как данная запись "была изменена другим пользователем"?

Значит, так. Вышеобсужденная кислотность (ACIDity) может достигаться различными способами. В основном различают два: pessimistic locking и optimistic locking.
Суть пессимистичной блокировки состоит в тоследующем: мы предпочитаем "раннее оповещение" о возможных конфликтах. Поэтому объект, к которому осуществляется доступ, блокируется таким образом, чтобы никто другой не мог выполнить конфликтующую операцию. То есть мы зашли в домик, дверь заперли и сидим, пока не закончим. Тот, кто попытается зайти туда же, получит от ворот поворот. В вышеприведенном примере это означает, что второй юзер просто не сможет отредактировать накладную NN.
Блокировки в такой системе бывают трех типов: Shared, Update, Exclusive.
Когда клиент (точнее, транзакция) пытается запросить какой-либо вид лока на определенный объект, то успех или неуспех операции зависит от наличия существующего лока на тот же объект, созданного другой транзакцией. В следующей таблице в столбцах идут существующие локи, а каждая строка соответствует запрашиваемому типу:
   |Shr|Upd|Exc|
Shr| + | + |   |
Upd| + |   |   |
Exc|   |   |   |

Плюсик означает, что операция будет успешна. Как видно, матрица имеет диагональный вид. Это естественно — совместимость локов штука симметричная. Смысл этой таблички в том, что те, кто хотят читать данные, получают перед этим shared lock. Из таблички видно, что они обломятся только в том случае, если кто-то уже захватил exclusive лок — это означает, что в данные прямо сейчас идет запись, и читать их нельзя. Все остальные виды локов, имеющиеся на объекте, не мешают нашему чтению.
Те, кто собирается писать данные, должны сначала получить exclusive lock, чтобы убедиться, что они никому не мешают. Такой лок не совместим ни с кем, и может быть выдан только один одновременно.
Промежуточный update lock введен в систему для того, чтобы решить такую проблему: когда кто-то сначала читает данные, а потом пытается их же писать. Если две таких транзакции выполняются одновременно (а это весьма вероятно, т.к. множество клиентов скорее всего будут выполнять однотипные действия), то у них будет хороший шанс зажать друг друга в угол. Дело в том, что если запрошенный лок конфликутет с уже имеющимися, то по умолчанию транзакция неопределенно долго ожидает снятия локов. Так что две транзакции спокойно получат по shared локу и каждая попытается сконвертировать его в exclusive. Но каждая сможет продолжить свою работу только после того, как другая отпустит shared lock. Итого — deadlock. Упс. Всех расстрелять.
Получение сразу exclusive лока не есть хорошо, поскольку если мы первые 90% процентов времени в транзакции тратим на чтение и 10% на запись, то 9/10 времени мы не даем другим доступа безо всякой к тому причины.
Глядя на схему совместимости update лока, можно понять для чего он предназначен. Он все еще дает остальным читать данные, но не даст никому ни писать, ни получить аналогичный лок, гарантируя успешное получение exclusive лока позднее.

Итак, с вами были пессимистичные блокировщики.

Далее в нашей программе — optimistic locking.
Эта стратегия на первый взгляд не похожа на блокировку вообще. Дело в том, что мы "запираем" данные только в момент commit транзакции, а длительные подготовки к этому моменту делаются безо всяких проверок конкурирующего доступа.
Ключевым фактором в оптимистичной блокировке является детектирование произошедших изменений.
Алгоритм коммита таков:
1. Проверить все данные, которые мы читали в транзакции, на наличие изменений с тех пор.
2. Если обнаружены изменения, то результат наших действий некорректен. Производим rollback, т.е. все изменения отменяются.
3. Если изменений не обнаружено, то можно сохранять изменения транзакции, делая их видимыми для окружающих.
Реализация этого алгоритма сделана по разному в разных системах. Например, в Interbase в саму структуру базы встроено отслеживание изменений в читаемых кем-либо данных. В MS SQL Server это делается при помощи сочетания timestamp-полей, которые гарантированно изменяют свое значение при хаписи в таблицу, и кратковременных пессимистичных блокировок в момент коммита (надо сказать, что сам сервер предоставляет только самый низкий уровень поддержки. Чтобы реализовать OL-систему на его основе, надо изрядно попотеть).

Резюме:
В вышеописанной ситуации картина для пользователей будет примерно такой, в зависимости от типа используемых блокировок:
1. Пессимистичные блокировки:
1.1. Немедленный откат: Потльзователь 2 получает при попытке зачитать накладную Упс! Объект зблокирован.
1.2. Бесконечное ожидание У Пользователя 2 прога виснет при чтении до тех пор, пока Пользователь 1 не сохранит/отменит свои изменения. После этого Пользователь 2 увидит корректное финальное состояние данных.
1.3. Конечное ожидание. Прога повисит некоторое время, и либо выпадет результат 1.1, либо 1.2, в зависимости от тормознутости Пользователя 1.
Резюме пессимистичных блокировок: Кто первым встал, того и тапки. Все проблемы касаются только Пользователя 2.
2. Оптимистичные блокировки:
Оба Пользователя увидят одну и ту же версию накладной — исходную. Дальнейший результат зависит от того, в каком порядке и какие действия они выполняют:
2.1. Пользователь 2 всего лишь хотел посмотреть чегой-то в этой накладной. Увидел, закрыл. Пользователь 1 ничего не заметил
2.2. Пользователь 2 шустро что-то поменял и сохранил накладную. Пользователь 1 при попытке сохранить накладную (но не раньше!) увидит "Изменения недействительны"
2.3. Пользователь 2 залез и начал что-то менять. Пользователь 1 сохранил накладную. Пользователь 2 при попытке сохранить получит тот же отказ "Изменения недействительны".

Таким образом:
1. При пессимистичных блокировках придется ждать, пока объект отпустят, даже если хотелось "всего лишь посмотреть"
2. При оптимистичных блокировках нет никакой возможности проверить, а не случилось ли страшное (кроме как истерично сохраняться каждые 20 секунд, надеясь подловить конкурента)

На самом деле, привденный пример реализации пессимизма слишком мрачен — при правильном управлении блокировками (shared->update->exclusive) можно добится высокой степени одновременности работы, все еще избегая потери изменений. Просто я и так чего-то увлекся... Люблю я эти вещи... транзакции, блокировки... кэши, индексы... оптимизация планов запросов, двухфазный коммит...
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.