Узнайте о базовых знаниях о создании транзакций в Sequelize и 🔐

Node.js
Узнайте о базовых знаниях о создании транзакций в Sequelize и 🔐

Sequelizeэто Node.jsORMбиблиотека, черезSequelizeМы можем использовать знакомую ссылку js для работы с базой данных. Потребности в работе, ежедневная работа автора в дополнение к вырезанию картинок, но и начал писать внутренний код. Из-за неопытности ранее разработанная функция имела исключение на случай параллелизма. После глубокого понимания вернитесь, чтобы разобраться в проблемах и записать учебные заметки.

Базовые знания

Сделка: транзакция представляет собой логическую единицу выполнения базы данных и состоит из ограниченной последовательности операций с базой данных. Эти операции, обернутые транзакцией, будут иметь общий результат выполнения, либо все завершатся успешно, либо с ошибкой, и все будут отброшены.all-or-nothing

Возьмите банковский перевод в качестве примера. Пользователь A передает 100 пользователю B.
Псевдокод (не обращайте внимания на подробности)

A 余额 = A余额 - 100
B 余额 = B余额 + 100

Учетная запись A -100 и учетная запись B +100 - это два отдельных оператора.Если в середине произойдет исключение, вызывающее прерывание программы, 100 на учетной записи A исчезнет из ниоткуда. Никто не хочет видеть такой результат, Чтобы обеспечить атомарность операции передачи, она обернута транзакцией. Если в середине произойдет ошибка, все будет откатываться.

start transaction;
// 转账操作
commit;

Демо в терминалеtransactionэффект.

Sequelizeпри условииTransactionкласс, черезSequelize.transactionСоздайте транзакцию и установите какую транзакцию, которая является текущей операцией, принадлежит каждый раз, когда операция базы данных.

await sequelize.transaction({}, async (transaction) => {
    const instance = await Accounts.findOne({
      where: {
        name: 'HelKyle',
      },
      transaction,
    });
    
    await instance.update({
      balances: instance.balances + number,
    }, {
      transaction,
    })
})

Как видно из журнала Squelize, идентификатор транзакции был создан как444a5afe-9635-40fd-90d7-10f5aa16077a, а последующие запросы и обновления выполняются в этой транзакции.

Executing (444a5afe-9635-40fd-90d7-10f5aa16077a): START TRANSACTION;
Executing (444a5afe-9635-40fd-90d7-10f5aa16077a): SELECT "name", "balances" FROM "accounts" AS "accounts" WHERE "accounts"."name" = 'HelKyle';
Executing (444a5afe-9635-40fd-90d7-10f5aa16077a): UPDATE "accounts" SET "balances"=$1 WHERE "name" = $2
Executing (444a5afe-9635-40fd-90d7-10f5aa16077a): COMMIT;

Если вы не хотите загружать его вручную каждый разtransactionобъект, который можно настроитьCLSспособ, настроить глобальный по умолчаниюtransaction.

Трагедия параллелизма

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

Транзакция 1 (А переводит деньги Б) Транзакция 2 (C переводит деньги A)
  Баланс запроса А 200
Баланс запроса А 200  
  Обновить баланс = 200 + 80
Обновить баланс = 200 - 100  

Как видно из результатов, А не получил в итоге 80 от С, а пользователь С проиграл 💰. Проблемы параллелизма можно избежать с помощью блокировки.

концепция замка

Пессимистическая блокировка против оптимистичной блокировки:

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

Транзакция 1 (А переводит деньги Б) Транзакция 2 (C переводит деньги A)
  Запросите баланс A 200 и заблокируйте запись
Запросите баланс А и обнаружите, что другие транзакции заблокировали записи, ожидая...  
  Обновите баланс A = 200 + 80, снимите блокировку
Получить право на исполнение, запросить баланс А, 280  
Обновить баланс = 280 - 100  

MySql,PostgresДостигнув пессимистической блокировки, вы можете выполнить соответствующие операторы (select for update), никаких разработок не требуется. Недостатком пессимистической блокировки является то, чтооперация чтенияВ частых сценариях это повлияет на пропускную способность.

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

Транзакция 1 (А переводит деньги Б) Транзакция 2 (C переводит деньги A)
  Баланс запроса А 200, номер версии n
Баланс запроса А 200, номер версии n  
  Обновление баланса = 200 + 80, версия = n + 1
Обнаружил, что последняя версия уже не n, попробуйте еще раз  
Запрос баланса 280, номер версии n + 1  
Обновление баланса = 280 - 100, номер версии = N + 2  

sql-код:

select name, balances, version from accounts where name='HelKyle';

update accounts set version=version+1, balances=balances+100
    where name='HelKyle' and version=#{version}

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

Эксклюзивная блокировка против общей блокировки:

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

SQL-код

select * from accounts where name='HelKyle' for update;

Сиквел

await Accounts.findOne({
    where: { name: 'HelKyle' },
    lock: Sequelize.Transaction.LOCK.UPDATE
});

Демо: 👈 Когда транзакция не завершена, 👉 транзакция может только ждать, пока не будет снята монопольная блокировка.

бизнес один дело два
start transaction; start transaction;
select * from accounts where name='A' for update;  
Выход: А 100  
  select * from accounts where name='A' for update;
  waiting...
commit;  
  Выход: A, 100
  commit;

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

sqlкод

select * from accounts where name='HelKyle' for share;

Sequelizeнаписание

await Accounts.findOne({
    where: { name: 'HelKyle' },
    lock: Sequelize.Transaction.LOCK.SHARE
});

Демо: 👈 👉 Транзакции могут быть запрошены, 👈 Когда транзакция хочет изменить данные, поскольку 👉 общая блокировка не снимается, операция модификации может только ждать.

бизнес один дело два
start transaction; start transaction;
select * from accounts where name='A' for share;  
Выход: А 100  
  select * from accounts where name='A' for share;
  Выход: А 100
update accounts set balances=10 where name='A'  
waiting...  
  commit;
set A.balances = 10  
commit;  

В дополнение к блокировке существует еще одна конфигурация, связанная с блокировкой.sequelize.transaction(options)параметры конфигурацииisolationLevel, поддерживает четыре уровня, а именно:

уровень грязное чтение неповторяемое чтение галлюцинации
READ_UNCOMMITTED чтение незафиксированных      
READ_COMMITTED зафиксированное чтение    
REPEATABLE_READ повторяющееся чтение  
СЕРИАЛИЗУЕМЫЙ

❌ из 👆 означает, что на этом уровне определенные типы проблем не возникнут.

Анализ существительных:

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

    бизнес один дело два
    start transaction; start transaction;
    select * from accounts where name='A';  
    Выход: А 100  
    update accounts set balances=10 where name='A'  
      select * from accounts where name='A';
      Вывод: A 10 (в настоящее время транзакция 1 не зафиксирована)
  • 不可重复读Описание В транзакции транзакция считывает универсальный ресурс несколько раз (в этой транзакции нет операции модификации) и получает разные результаты.

    бизнес один дело два
    start transaction; start transaction;
    select * from accounts where name='A';  
    Выход: А 100  
      update accounts set balances=10 where name='A'
      commit;
    select * from accounts where name='A';  
    Выход: А 10  
  • 幻读, что означает, что есть запрос, удовлетворяющий условию запроса, но его раньше не было. Например, queryAll для всех данных в таблице, установите балансы на 0, но поскольку другие транзакции одновременно записывают новое содержимое, новая запись явно соответствует queryAll, но балансы не равны 0, например 👻.

    бизнес один дело два
    start transaction; start transaction;
    select * from accounts;  
    Выход: А 100  
    update accounts set balances=0;  
      insert into accounts values ('B', 100);
      commit;
    commit;  
    select * from accounts;  
    Выход: А 0, В 100  

Настройка уровня изоляции в Sequelize

sequelize.transaction({
	isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE
}, transaction => {
  // your transactions
});

Ссылки по теме