Статьи

Использование именованных блокировок базы данных

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

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

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
@Before("execution(* *.*(..)) && @annotation(updateLock)")
    public void applyUpdateLocking(JoinPoint joinPoint, UpdateLock updateLock) {
        int entityTypeId = entityTypeIds.get(updateLock.entity());
        // note: letting the long id overflow when fitting into an int, because the postgres lock function takes only ints
        // lock collisions are pretty unlikely and their effect will be unnoticeable
        int entityId = (int) getEntityId(joinPoint.getStaticPart().getSignature(), joinPoint.getArgs(),
                updateLock.idParameter());
         
        if (entityId != 0) {
            logger.debug("Locking on " + updateLock.entity() + " with id " + entityId);
            // using transaction-level lock, which is released automatically at the end of the transaction
            final String query = "SELECT pg_advisory_xact_lock(" + entityTypeId + "," + entityId + ")";
            em.unwrap(Session.class).doWork(new Work() {
                @Override
                public void execute(Connection connection) throws SQLException {
                    connection.createStatement().executeQuery(query);
                }
            });
        }
     }

Что оно делает:

  • Он ищет методы, аннотированные @UpdateLock и применяет аспект
  • аннотация UpdateLock имеет два атрибута — тип объекта и имя параметра метода, который содержит идентификатор, для которого мы хотим заблокировать обновления
  • у entityTypeIds основном есть соответствие между строковым именем сущности и произвольным числом (потому что функция postgres требует число, а не строку)

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

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

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