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
});