Блокировки строк и транзакций SQL - PullRequest
0 голосов
/ 20 апреля 2019

Я действительно новичок в реляционных базах данных. Я работаю над проектом, который связан с финансами, и поэтому я хочу, чтобы любые действия, влияющие на баланс, не происходили одновременно, и я хочу добиться этого с помощью блокировок, однако я не уверен, как их использовать. Видение у меня сейчас: Я хочу иметь отдельную таблицу для каждого действия и поле баланса в таблице пользователей, значение которого будет получено из всех соответствующих таблиц. Как это ни печально, я никогда не собираюсь обновлять существующие записи - только добавляя их. Я хочу убедиться, что в эти таблицы одновременно вставляется только одна запись для каждого пользователя. Например: 3 транзакции происходят одновременно, и поэтому 3 записи будут добавлены в любые соответствующие таблицы. Две записи имеют одинаковый идентификатор пользователя, внешний ключ к моей таблице пользователей, а другая - другой. Я хочу, чтобы мои записи с теми же внешними ключами передавались по конвейеру, а другую можно сделать в любое время. Как мне этого добиться? Есть ли лучшие способы подойти к этому?

Ответы [ 2 ]

0 голосов
/ 28 апреля 2019
  1. Используйте ENGINE=InnoDB для всех ваших таблиц.
  2. Используйте транзакции:

    BEGIN;
    do all the work for a single action
    COMMIT;
    

Классическим примером одного действия являетсяснять деньги с одного счета и добавить его на другой счет.Удаление будет включать проверку овердрафта, и в этом случае вы получите код ROLLBACK вместо COMMIT.

Полученные блокировки гарантируют, что все для одного действия либо полностью выполнено, либо ничеговсе сделано.Это применимо даже в случае сбоя системы между BEGIN и COMMIT.

Без начала и фиксации, но с autocommit = ON, каждый оператор неявно окружен begin и commit.Это пример UPDATE в предыдущем ответе - «атомарный».Однако, если деньги, вычтенные с одного счета, необходимо добавить на другой счет, что произойдет, если сбой произойдет сразу после UPDATE?Деньги исчезают.Итак, вам действительно нужно

 BEGIN;
 if not enough funds, ROLLBACK and exit
 UPDATE to take money from one account
 UPDATE to add that money to another account
 INSERT into some log or audit trail to track all transactions
 COMMIT;

Проверять после каждого шага - ОТКЛЮЧИТЬ и предпринимать уклончивые действия при любой неожиданной ошибке.

Что произойдет, если 2 (или более) действия произойдут в одновремя "?

  • Один ждет другого.
  • Существует тупик, и на него навязывается ОТВЕРТКА.

Но, ни в коем случаеБудут ли испорчены данные.

Еще одно примечание ... В некоторых случаях вам нужно FOR UPDATE:

BEGIN;
SELECT some stuff from a row FOR UPDATE;
test the stuff, such as account balance
UPDATE that same row;
COMMIT;

FOR UPDATE говорит другим темам "Держите рукиВне этой строки я могу изменить его; пожалуйста, подождите, пока я не закончу ".Без FOR UPDATE другой поток мог бы проникнуть и истощить счет денег, которые, как вы думали, были там.

Комментарии к некоторым из ваших мыслей:

  • Одной таблицы обычно достаточно длямного пользователей и их аккаунт.Он будет содержать «текущий» баланс для каждой учетной записи.Я упомянул «бревно»;это будет отдельная таблица;он будет содержать «историю» (в отличие от «текущей» информации).
  • FOREIGN KEYs в данном обсуждении в основном не имеют значения.Они служат 2 целям: убедитесь, что в другой таблице есть строка, которая должна быть там;и неявно создайте INDEX, чтобы ускорить эту проверку.
  • Конвейер?Если вы выполняете не более ста «транзакций» в секунду, вам нужно беспокоиться о логике BEGIN..COMMIT.
  • «В то же время» и «одновременно» - неправильно используемые термины.Маловероятно, что два пользователя будут одновременно обращаться к базе данных - учитывая задержки браузера, сетевые задержки, задержки ОС и т. Д. Плюс тот факт, что большинство этих шагов заставляют активность переходить в один файл.Сеть заставляет одно сообщение попасть туда раньше другого.Между тем, если одна из ваших «транзакций» занимает 0,01 секунды, кого это волнует, должен ли «одновременный» запрос дождаться его завершения?Дело в том, что то, что я описал, заставит «ждать», если это необходимо, чтобы избежать путаницы в данных.

Все это говорит о том, что все еще могут быть некоторые «в то же время» - если транзакциине касайтесь одних и тех же строк, тогда несколько миллисекунд, которые требуются от BEGIN до COMMIT, могут перекрываться.Рассмотрим график двух транзакций, которые произошли почти одновременно:

BEGIN;  -- A
pull money from Alice  -- A
      BEGIN;   -- B
      pull money from Bobby  -- B
give Alice's money to Alan  -- A
      give Bobby's money to Betty  --B
COMMIT;   --A
      COMMIT;  --B
0 голосов
/ 20 апреля 2019

Я хочу, чтобы любые действия, влияющие на баланс, не происходили одновременно

Почему?

Я хочу добиться этого с помощью замков

Почему?

Чтобы дать вам контрпример. Допустим, вы хотите избежать отрицательных остатков на счетах. Когда пользователь снимает 500 $, как вы можете смоделировать это без блокировок.

UPDATE accounts
   SET balance = balance - 500
 WHERE accountholderid = 42
   AND balance >= 500

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

(я знаю, что MySQL все еще получит блокировку строки)

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

...