Производственная авария, вызванная оператором Insert into select

MySQL

предисловие

Пожалуйста, используйте   Вставить в выбор с осторожностью. В этот день xxx получил запрос на перенос данных из таблицы A в таблицу B для резервного копирования. Хотел сначала через программу проверить, а потом уже пачками вставлять. Но xxx посчитал, что это немного медленно и требует много сетевого ввода-вывода, и решил использовать другой метод для его реализации. Плавая в океанах Baidu, он обнаружил, что может использоватьinsert into selectРеализовано так, что вы можете избежать использования сетевого ввода-вывода и полагаться на ввод-вывод базы данных напрямую с помощью SQL, что просто не очень хорошо. Затем его уволили.

Произошла авария.

   По данным в базеorder_todayОбъем данных слишком велик, кажется, что в то время он составлял 700 Вт, и он увеличивается со скоростью 30 Вт каждый день. Итак, босс приказал хххorder_todayЧасть данных при миграции наorder_record, и воляorder_todayУдаление данных в . это уменьшаетorder_todayКоличество данных в таблице.

Учитывая, что ввод-вывод БД будет занят, чтобы не влиять на бизнес, планирую начать миграцию после 9:00, но в 8:00 ххх попытался мигрировать небольшую часть данных (1000 записей ), и почувствовал, что проблемы нет, просто начните думать о массовых миграциях.

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

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

восстановление после аварии

   Создайте компактную версию базы данных локально и сгенерируйте 100 Вт данных. Моделируйте то, что происходит в Интернете.

создать структуру таблицы

форма заказа

CREATE TABLE `order_today` (
  `id` varchar(32) NOT NULL COMMENT '主键',
  `merchant_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '商户编号',
  `amount` decimal(15,2) NOT NULL COMMENT '订单金额',
  `pay_success_time` datetime NOT NULL COMMENT '支付成功时间',
  `order_status` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '支付状态  S:支付成功、F:订单支付失败',
  `remark` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '备注',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间 -- 修改时自动更新',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `idx_merchant_id` (`merchant_id`) USING BTREE COMMENT '商户编号'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Бланк записи заказа

CREATE TABLE order_record like order_today;

Данные таблицы заказов на сегодня

Моделирование миграции

Перенести все данные до 8 числа вorder_recordиди к столу.

INSERT INTO order_record SELECT
    * 
FROM
    order_today 
WHERE
    pay_success_time < '2020-03-08 00:00:00';

Запустите перенесенный sql в navicat, откройте другое окно для одновременной вставки данных и смоделируйте размещение заказа.


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

Причина появления

   При уровне изоляции транзакций по умолчанию:insert into order_record select * from order_todayПравила блокировки:order_recordзамок стола,order_todayПрогрессивные замки (сканирование замков один за другим).

Проанализируйте процесс исполнения.

Наблюдаяперенести sqlреализации вы найдетеorder_todayЭто полное сканирование таблицы, что означает, что выполняетсяinsert into select fromзаявление, mysql будет сканировать сверху внизorder_todayЗаписи в нем блокируются и блокируются, так что это не то же самое, что блокировать напрямую таблицу.

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

решение

  Из-за условий запросаorder_todayПолное сканирование таблицы, что может избежать полного сканирования таблицы, очень просто, дайтеpay_success_timeдобавить полеidx_pay_suc_timeпоказательВот и все, поскольку используется индексный запрос, таблица не будет заблокирована из-за сканирования всей таблицы, а будут заблокированы только те записи, которые удовлетворяют условиям.

окончательный sql

INSERT INTO order_record SELECT
    * 
FROM
    order_today FORCE INDEX (idx_pay_suc_time)
WHERE
    pay_success_time <= '2020-03-08 00:00:00';

Процесс реализации

Суммировать

использоватьinsert into tablA select * from tableBзаявление, обязательноtableBНазадwhere,orderили другие условия, необходимо иметь соответствующиепоказатель, чтобы избежатьtableBВсе записи заблокированы.

Справочная статья

конец

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