Оптимистическая блокировка на конкретном примере (Java)

Я потратил свое утро на чтение всех популярных статей, которые Google тратят на оптимистичную блокировку, и на протяжении всей жизни мне, я все еще не совсем понимаю.

Я понимаю, что оптимистическая блокировка включает добавление столбца для отслеживания «версии» записи и что этот столбец может быть меткой времени, счетчиком или любой другой конструкцией отслеживания версий. Но я до сих пор не понимаю, как это обеспечивает целостность WRITE (это означает, что если несколько процессов одновременно обновляют один и тот же объект, то впоследствии объект правильно отражает истинное состояние, в котором он должен находиться).

Может ли кто-нибудь предоставить конкретный, простой для понимания пример того, как оптимистическая блокировка может использоваться в Java (по сравнению, возможно, с базой данных MySQL). Допустим, у нас есть Person сущность:

public class Person {
    private String firstName;
    private String lastName;
    private int age;
    private Color favoriteColor;
}

И эти Person экземпляры сохраняются в people таблице MySQL:

CREATE TABLE people (
    person_id PRIMARY KEY AUTO_INCREMENT,
    first_name VARCHAR(100) NOT NULL,
    last_name VARCHAR(100) NOT NULL,        # } I realize these column defs are not valid but this is just pseudo-code
    age INT NOT NULL,
    color_id FOREIGN KEY (colors) NOT NULL  # Say we also have a colors table and people has a 1:1 relationship with it
);

Теперь предположим, что есть две программные системы или одна система с двумя потоками, которые пытаются одновременно обновить одну и ту же Person сущность:

  • Программа / поток №1 пытается сохранить смену фамилии (с «Джон Смит» на «Джон Доу»)
  • Программа / поток №2 пытается сохранить изменение любимого цвета (с КРАСНОГО на ЗЕЛЕНЫЙ)

Мои вопросы:

  1. Как можно реализовать оптимистическую блокировку для таблиц people и / или colors? (Ищем конкретный пример DDL)
  2. Как тогда можно было бы использовать эту оптимистичную блокировку на уровне приложения / Java? (Ищем конкретный пример кода)
  3. Может ли кто-нибудь провести меня по сценарию, в котором изменения DDL / кода (из пунктов №1 и №2 выше) будут задействованы в моем сценарии (или любом другом сценарии) и будут «оптимистично блокировать» таблицы _9 _ / _ 10_? По сути, я хочу увидеть в действии оптимистичную блокировку с понятным объяснением того, почему она работает.

person AdjustingForInflation    schedule 14.01.2014    source источник
comment
Что вы хотите заблокировать: объекты Java в памяти или записи в базе данных?   -  person Philip Frank    schedule 14.02.2014


Ответы (2)


Обычно, когда вы изучаете оптимистическую блокировку, вы также используете такую ​​библиотеку, как Hibernate. или другую реализацию JPA с поддержкой @Version.

Пример можно было бы прочитать так:

public class Person {
    private String firstName;
    private String lastName;
    private int age;
    private Color favoriteColor;
    @Version
    private Long version;
}

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

Тогда DDL может быть

CREATE TABLE people (
    person_id PRIMARY KEY AUTO_INCREMENT,
    first_name VARCHAR(100) NOT NULL,
    last_name VARCHAR(100) NOT NULL,        # } I realize these column defs are not valid but this is just pseudo-code
    age INT NOT NULL,
    color_id FOREIGN KEY (colors) NOT NULL,  # Say we also have a colors table and people has a 1:1 relationship with it
    version BIGINT NOT NULL
);

Что происходит с версией?

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

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

UPDATE Person SET lastName = 'married', version=2 WHERE person_id = 42 AND version = 1;

После выполнения оператора вы проверяете, обновили ли вы строку или нет. Если вы это сделали, никто другой не изменил данные с тех пор, как вы их прочитали, иначе кто-то другой изменил данные. Если кто-то изменил данные, вы обычно получите OptimisticLockException от библиотеки, которую вы используют.

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

Так что столкновения нет:

  1. Процесс А читает Человек
  2. Процесс A записывает Person, тем самым увеличивая версию
  3. Процесс B читает "Человек"
  4. Процесс B пишет Person, тем самым увеличивая версию

Столкновение:

  1. Процесс А читает Человек
  2. Процесс B читает "Человек"
  3. Процесс A записывает Person, тем самым увеличивая версию
  4. Процесс B получает исключение при попытке сохранить, поскольку версия изменилась с момента чтения Person

Если Color - это другой объект, вы должны поместить туда версию по той же схеме.

Что не является оптимистической блокировкой?

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

Какой тип столбца использовать в качестве версии?

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

version TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;

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

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

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

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

В крайнем случае вы можете использовать значение всех полей как версию. Хотя в большинстве случаев это будет наиболее затратным подходом, это способ получить аналогичные результаты без изменения структуры таблицы. Если вы используете Hibernate, есть @OptimisticLocking -аннотация для обеспечения соблюдения это поведение. Используйте @OptimisticLocking(type = OptimisticLockType.ALL) в классе сущности, чтобы сбой произошел, если какая-либо строка изменилась после того, как вы прочитали сущность, или @OptimisticLocking(type = OptimisticLockType.DIRTY), чтобы просто потерпеть неудачу, когда другой процесс также изменил поля, которые вы также изменили.

person TheConstructor    schedule 14.02.2014
comment
Небольшой вопрос, сэр? Скажем, в системах с высокой степенью параллелизма, является ли оптимистичная блокировка хорошей идеей? Рассмотрим сценарий из 100 потоков, все читают одну и ту же версию, и один из них, обновляющий версию, оставит остальные 99 потоков с исключением. Это будет означать 99% недоступность! Как нам поступать с этим аспектом? Пожалуйста помоги. - person Shubham Kumar; 22.02.2017
comment
@ShubhamKumar, который может зависеть от того, что эти 100 потоков делают с данными. Иногда вы можете разделить данные на более мелкие части, иногда вы можете устранить необходимость явной блокировки, сформулировав sql для всего обновления, например. увеличивая счетчик, и иногда вам могут потребоваться настоящие блокировки, поэтому обновления просто выполняются одно за другим. Оптимистическая блокировка работает лучше всего, когда обычно только один поток обновляет каждую версионную сущность, иначе поток может бесконечно повторять попытки. - person TheConstructor; 22.02.2017
comment
Что, если все потоки пытаются обновить один и тот же объект с версией? Есть ли оптимистическая блокировка для этого же - хорошая идея? - person Shubham Kumar; 22.02.2017
comment
Оптимистическая блокировка @ShubhamKumar гарантирует, что потоки не перезапишут изменения другого потока. Если потоку не позволяют обновить запись, он должен прочитать текущее состояние и повторно применить изменение. Но он может снова выйти из строя, поскольку до 98 других потоков делают то же самое. Так что, если это пик 100, обычно 1, это может быть нормально, в противном случае я бы посоветовал этого не делать. - person TheConstructor; 22.02.2017
comment
В случае обновления нескольких таблиц будет ли достаточно использовать оптимистическую блокировку для каждой таблицы + запуск транзакции в дБ? - person dierre; 11.02.2018
comment
@dierre, который зависит от изоляции транзакции и того, как связаны объекты и их обновления. Если один процесс обновляет только таблицу A, а другой - только таблицу B одновременно, оба могут быть успешными. Это может быть, а может и не быть проблемой. Вы можете определить общую версию, которая будет обновляться при любом изменении любой из этих таблиц, и обновить эту версию в транзакции. Это, конечно, менее оптимистично (т. Е. Более длительное, возможно блокирующее транзакции), но может помочь. - person TheConstructor; 13.02.2018
comment
Я говорю о таблицах со связями между ними. Таким образом, они будут обновляться в том же процессе (с учетом идентификатора). - person dierre; 13.02.2018

@TheConstructor: отличное объяснение того, что это такое, а что нет. Когда вы сказали: «Оптимистическая блокировка - это не волшебство для объединения конфликтующих изменений», я хотел прокомментировать. Раньше я управлял приложением DataFlex, которое позволяло пользователям редактировать записи в форме. Когда они нажимали кнопку «Сохранить», приложение выполняло так называемое «многопользовательское повторное чтение» данных - извлекало текущие значения - и сравнивало с тем, что пользователь изменил. Если поля, измененные пользователем, не были изменены за это время, то только эти поля будут записаны обратно в запись (которая была заблокирована только во время операции повторного чтения + записи), и, следовательно, 2 пользователя могли прозрачно изменять различные поля на та же запись без проблем. Это не требовало штампа версии, просто было известно, какие поля были изменены.

Конечно, это не идеальное решение, но в данном случае оно помогло. Это было оптимистично, позволяло вносить несвязанные изменения и выдавало пользователю ошибку при конфликтующих изменениях. Это было лучшее, что можно было сделать до SQL, но он по-прежнему является хорошим принципом проектирования сегодня, возможно, для большего количества сценариев, связанных с объектами или сетью.

person no comprende    schedule 13.11.2014
comment
У меня нет представителя, чтобы прокомментировать фактический вопрос, поэтому: Пример с Person и Color: Person имеет идентификатор объекта, поэтому это то, что изменяется, таблица цветов связана, а не первична (вы отметили ее как внешний ключ ), поэтому дополнения к нему никогда не столкнутся. По-видимому, вы не можете удалить из него строки, даже если человек меняет свой цвет? Вы можете осмысленно обновлять только одну сущность за раз, иначе вы попадете в область каскадных обновлений и т. Д. Я думаю, что ваша модель напряжена. Вместо этого сделать цвет уникальным атрибутом человека? - person no comprende; 13.11.2014
comment
Иногда многопользовательское перечитывание - хорошая идея, иногда - нет. Например. у вас есть сотрудник, который отмечает пользователей, которые возвращают почту, чтобы они не могли размещать заказы. Одновременно может быть изменен адрес, что обычно приводит к удалению флага отсутствия доставки. В этом сценарии лучше всего отклонить второе редактирование и спросить пользователя, действительно ли изменение применимо к измененным данным. Это легко сделать с помощью оптимистической блокировки. - person TheConstructor; 27.11.2014
comment
На самом деле это хорошая идея, и ее можно наложить поверх оптимистической блокировки с полем версии (то есть, если поле версии не изменилось, выполните обновление, если оно изменено, тогда посмотрите, изменяем ли мы поля, которые не были изменены, так как мы изначально вытащили запись). Самая большая практическая проблема, которую я вижу, заключается в том, что вы должны знать предыдущие значения, например вы редактируете v4, вы переходите к сохранению, а запись уже находится на v6 [сделано и зафиксировано в базе данных], как узнать, какие поля были изменены? Решение возможно, но может быть непросто. Форма, вероятно, должна будет отправить old_value - ›new_value. - person Brad Peabody; 04.06.2018