MySQL / InnoDB транзакции с блокировками таблиц в InnoDB - PullRequest
0 голосов
/ 31 мая 2018

Я провел много исследований и нашел много информации по всем актуальным темам.Однако я не уверен, что теперь понимаю, как правильно собрать всю эту информацию.

Это приложение написано на PHP.

Для запросов я использую PDO.

База данных MySQL настроена как InnoDB.

Что мне нужно

SELECT ... FROM tableA;

// PHP looks at what comes back and does some logic.
INSERT INTO tableA ...;
INSERT INTO tableB ...;

Условия:

  • INSERT должны быть атомарными.Если произойдет сбой одного из них, я хочу выполнить откат.
  • Не допускается чтение и запись из / в таблицу A между SELECT и ВСТАВКОЙ из / в таблицу A.

Этодля меня это выглядит как очень простая проблема.И все же я не могу понять, как это сделать правильно.Итак, мой вопрос:

Каков наилучший подход?

Это схема моего текущего плана, сильно упрощенная:

try {
  SET autocommit = 0;

  BLOCK TABLES tableA WRITE, tableB WRITE;

  SELECT ... FROM tableA;

  INSERT INTO tableA ...;
  INSERT INTO tableB ...;

  COMMIT;

  UNLOCK TABLES;

  SET autocommit = 1;
}

catch {
  ROLLBACK;

  UNLOCK TABLES;

  SET autocommit = 1;
}     

Я чувствую, что естьмного чего можно сделать лучше, но я не знаю как: /

Почему это так?

  • Мне нужна какая-то транзакция, чтобы сделать откатв случае сбоя INSERT.
  • Мне нужно заблокировать tableA, чтобы убедиться, что другие INSERT или UPDATE не выполняются.
  • Транзакции и блокировки таблицы не работают вместе (https://dev.mysql.com/doc/refman/8.0/en/lock-tables-and-transactions.html)
  • Я хочу использовать автокоммит в качестве стандарта для всей остальной части моего приложения, поэтому в конце я установил его на «1».
  • Я действительно не уверен в этом: Но я где-тообнаружил, что после блокировки таблицы, я могу (из текущего соединения) только запросить эту таблицу, пока я не разблокирую ее (для меня это не имеет смысла). Вот почему я тоже заблокировал tableB, хотя в противном случае я бы не сталт. нужно.

Я открыт для совершенно разныхNT подходит

Я открыт для любых предложений в рамках рамочных условий PHP, MySQL, PDO и InnoDB.

Спасибо!


Редактировать 1 (2018-06-01)

Мне кажется, что моя проблема / вопрос требует дополнительного разъяснения.

Начальная точка:

Если имеется две таблицы, t1 и t2 .

t1 имеетнесколько столбцов неуникальных значений.

Специфика t2 не имеет значения для этой проблемы.

Что я хочу сделать:

Шаг за шагомшаг:

  1. Выберите несколько столбцов и строк из t1 .

  2. В PHP анализируйте полученные данные.На основании результатов этого анализа соберите набор данных.

  3. ВСТАВЬТЕ части набора данных в t1 и части его в t2 .

Дополнительная информация:

  • ВСТАВКИ в 2 таблицы должны быть атомарными.Это может быть достигнуто с помощью транзакций.
  • Между шагами 1 и 3 не допускается вставка из другого соединения. Это очень важно, потому что каждая вставка в t1 имеетпроисходить с полным осознанием текущего состояния таблицы.Я лучше опишу это более подробно.Я пока опущу t2 , чтобы облегчить понимание.

    Представьте себе эту последовательность событий (соединения con1 и con2):

    1. con1: ВЫБРАТЬ ... ОТ t1 ГДЕ xyz;
    2. con1: PHP обрабатывает информацию.
    3. con2: ВЫБРАТЬ ... ОТ t1 ГДЕ uvw;
    4. con2: PHPобрабатывает информацию.
    5. con1: INSERT INTO t1 ...;
    6. con2: INSERT INTO t1 ...;

    Итак, оба соединения см. t1 в том же состоянии.Тем не менее, они выбирают другую информацию. Con1 берет собранную информацию, выполняет с ней некоторую логику, а затем вставляет данные в новую строку в t1 . Con2 делает то же самое, но использует другую информацию.

    Проблема заключается в следующем: оба соединения вставили данные, основанные на вычислениях, которые не учитывали все другие соединения, вставленные в t1 , потому что этой информации не было, когда они читали из t1.

    Con2 мог вставить строку в t1 , которая бы соответствовала WHERE-условиям SELECT- con1 .заявление.Другими словами: если бы con2 вставил свою строку ранее, con1 , возможно, создал совершенно другие данные для вставки в t1 .Это значит: два INSERT могли полностью аннулировать вставки друг друга.

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

    Надеюсь, это немного прояснит ситуацию ...: /

Мысли:

Мои мысли были:

  1. Мне нужно сделать вставки в две таблицы атомарными.-> Я буду использовать транзакцию для этого.Примерно так:

    try {
      $pdo->beginTransaction();
      // INSERT INTO t1 ...
      // INSERT INTO t2 ...
      $pdo->commit();
    }
    catch (Exception $e) {
      $pdo->rollBack();
      throw $e;
    }
    
  2. Мне нужно убедиться, что никакое другое соединение не пишет и не читает из t1 .Именно здесь я решил, что мне нужны LOCK TABLES.

  3. Предполагая, что мне пришлось использовать LOCK TABLES, я столкнулся с проблемой, что LOCK TABLES не распознает транзакции.Вот почему я решил пойти с предложенным здесь решением (https://dev.mysql.com/doc/refman/8.0/en/lock-tables-and-transactions.html), а также в нескольких ответах на stackoverflow.

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


Редактировать 2 (2018-06-01)

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

1 Ответ

0 голосов
/ 01 июня 2018

Случай 1:

BEGIN;
INSERT ..
INSERT ..
COMMIT;

Другие соединения не увидят вставленные строки до завершения фиксации.Таким образом, BEGIN...COMMIT сделал две вставки "атомарными".

Если что-то не получается, вам все равно нужна попытка / отлов, чтобы справиться с этим.

Не используйте LOCK TABLES на InnoDBтаблицы.

Не беспокойтесь autocommit;BEGIN..COMMIT переопределяет его.

Мои утверждения применимы (вероятно) ко всем фреймворкам.(За исключением того, что некоторые из них не имеют «try» и «catch».)

Случай 2 : заблокировать строку в ожидании ее возможного изменения:

BEGIN;
SELECT ... FROM t1 FOR UPDATE;
... work with the values SELECTed
UPDATE t1 ...;
COMMIT;

Это удерживает других от строк SELECTed до тех пор, пока после COMMIT.

Случай 3 : Иногда IODKU полезно делать две вещи в одном атомарном операторе:

INSERT ...
    ON DUPLICATE KEY UPDATE ...

вместо

BEGIN;
SELECT ... FOR UPDATE;
if no row found
    INSERT ...;
else
    UPDATE ...;
COMMIT;

Класс 4 : Пример классического банковского обслуживания:

BEGIN;
UPDATE accounts SET balance = balance - 1000.00 WHERE id='me';
... What if crash occurs here? ...
UPDATE accounts SET balance = balance + 1000.00 WHERE id='you';
COMMIT;

Если система падает между двумя UPDATEs,Первое обновление будет отменено.Это удерживает систему от потери отслеживания перевода.

Случай 5: Возможно, близко к тому, что хочет ОП.В основном это комбинация случаев 2 и 1.

BEGIN;
SELECT ... FROM t1 FOR UPDATE;   -- see note below
... work with the values SELECTed
INSERT INTO t1 ...;
COMMIT;

Примечания к случаю 5: SELECT..FOR UPDATE должен содержать все строки, которые вы не хотите видеть в другом соединении.Это приводит к задержке другого соединения до этого соединения COMMITs.(Да, это похоже на LOCK TABLES t1 WRITE.)

Случай 6: «Обработка», которая должна быть внутри BEGIN..COMMIT, займет слишком много времени.(Пример: типичная онлайн-корзина.)

Для этого требуется механизм блокировки вне транзакций InnoDB.Один из способов (полезен для корзины) - использовать строку в какой-то дополнительной таблице и попросить всех проверить ее.Другой способ (более практичный в рамках одного соединения) - использовать GET_LOCK('foo') и его друзей.

Общие обсуждения

Все приведенные выше примеры блокируют только строку (s) участвует, а не вся таблица (ы).Это делает действие намного менее инвазивным и позволяет системе справляться с гораздо большей активностью.

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

«Предотвращение вставок». Если MVCC запустить SELECT, это похоже на получение моментального снимка всего, на что вы смотрите.Вы не увидите INSERTs до тех пор, пока не завершите транзакцию, в которой находится SELECT. Вы также можете съесть свой торт и съесть его.Таким образом, кажется, что вставки были заблокированы, но вы получаете выигрыш в производительности от их параллельного выполнения.Магия.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...