Заполните исторические варианты получения перед ужесточением правил

MySQLBeginner
Практиковаться сейчас

Введение

Столбец пункта выдачи магазина уже добавлен, со значением counter по умолчанию для старого клиента. Историческим заказам всё ещё нужны утверждённые варианты получения. Более поздние заказы уже поступили: один пропустил новый столбец, другой явно выбрал garden. Широкое заполнение истории могло бы перезаписать этот допустимый новый выбор.

Вы подготовите backfill.sql и tighten.sql, примените точные утверждённые исторические соответствия и сохраните контракты обоих клиентов. Предварительно нужны понятия предыдущего занятия плюс базовые SELECT/WHERE и UPDATE; JOIN, агрегирование, временные защитные проверки и шаблоны транзакций объясняются здесь. Это независимое занятие в Terminal имеет собственные расширенную таблицу, данные и отдельный MySQL. Файлы, соединения и прогресс из предыдущей VM не переносятся.

Обновляйте только orders.pickup_code для утверждённых исторических ID и ужесточайте тип, значение по умолчанию, допустимость NULL и правило ссылки этого столбца. Сохраните исходные записи заказов, связи товарных позиций, более поздние варианты получения, таблицы утверждённых соответствий и пунктов выдачи, демонстрационную таблицу и подготовленные инструменты. Разрешено добавить только учебные заказы 301 и 302 ниже. Запросы последовательны в контролируемом окне обслуживания без конкурирующего процесса изменения утверждённых данных. Это не реализация онлайн-миграции, распределённой координации или автоматического отката DDL. Оценка времени автором включает чтение и обычные ошибки, без данных о времени прохождения новичками.

Примените только полные утверждённые исторические варианты

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

Начните во вкладке Terminal и перейдите в этот независимый рабочий каталог:

cd /home/labex/project/pickup-desk
./shop status
./shop map
./shop points

Историческое множество — legacy_orders: ID 101, 102 и 103. Утверждённые соответствия указывают north для 101, counter для 102 и garden для 103. Исходные суммы остаются 2100, 3400 и 0. Два заказа Sam нельзя соединять по имени покупателя. Более поздние ID 201 и 202 находятся вне исторического множества: сохраните counter для 201 и garden для 202. Их товарные позиции также должны сохраниться.

Подсчитайте утверждённые строки для каждого исторического идентификатора. COUNT(*) считает строки. GROUP BY order_id создаёт группу для каждого ID; HAVING фильтрует группы после подсчёта. <> означает «не равно». Этот запрос возвращает дублирующиеся группы или другие группы, содержащие не одну строку:

./db -e "SELECT order_id,COUNT(*) FROM approved_pickups GROUP BY order_id HAVING COUNT(*)<>1;"

Ожидается пустой результат. Он не может выявить полностью отсутствующий ID, поскольку отсутствующая строка не образует группы. LEFT JOIN сохраняет каждую строку слева, с NULL в правых полях при отсутствии соответствия. l и m — короткие псевдонимы таблиц; ON связывает точные ID заказов. IS NULL выбирает отсутствующие соответствия справа:

./db -e "SELECT l.order_id FROM legacy_orders l LEFT JOIN approved_pickups m ON m.order_id=l.order_id WHERE m.order_id IS NULL;"

Также требуйте наличия каждого кода соответствий в pickup_points и принадлежности каждого ID соответствий к legacy_orders. В этой проверке связи код NULL считается не имеющим соответствия:

./db -e "SELECT m.order_id,m.pickup_code FROM approved_pickups m LEFT JOIN pickup_points p ON p.code=m.pickup_code WHERE p.code IS NULL;"
./db -e "SELECT m.order_id FROM approved_pickups m LEFT JOIN legacy_orders l ON l.order_id=m.order_id WHERE l.order_id IS NULL;"

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

Временная таблица принадлежит одному соединению с базой данных и исчезает при его закрытии. Наша небольшая защитная таблица хранит только целый ноль: CHECK(errors=0) отклоняет ненулевой счётчик, а NOT NULL — отсутствующий. INSERT ... SELECT COUNT(*) вставляет число нарушений. Если любая вставка защитной проверки завершается ошибкой, штатный пакет останавливается до рабочего UPDATE и закрывает соединение. Ожидающие фиксации изменения данных InnoDB откатываются. ENGINE=InnoDB выбирает для временной таблицы механизм хранения MySQL с поддержкой транзакций. Сама временная таблица не является откатываемым изменением постоянной схемы. Каждый apply или trial начинает новое соединение, поэтому повторно использовать прежнюю временную таблицу не нужно.

Сохраните полный файл с помощью here-document с маркером в кавычках из предыдущего занятия. Производный запрос в скобках с именем duplicates превращает группы не из одной строки в строки, которые внешний COUNT может посчитать. UPDATE JOIN меняет только заказы с точным утверждённым ID; он никогда не присваивает значение по умолчанию всем заказам. COMMIT делает изменение данных постоянным лишь после успешных защитных проверок:

cat > backfill.sql <<'SQL'
CREATE TEMPORARY TABLE map_guard (
    errors INT NOT NULL CHECK(errors=0)
) ENGINE=InnoDB;
START TRANSACTION;
INSERT INTO map_guard
SELECT COUNT(*) FROM legacy_orders l
LEFT JOIN approved_pickups m ON m.order_id=l.order_id
WHERE m.order_id IS NULL;
INSERT INTO map_guard
SELECT COUNT(*) FROM (
    SELECT order_id FROM approved_pickups
    GROUP BY order_id HAVING COUNT(*)<>1
) duplicates;
INSERT INTO map_guard
SELECT COUNT(*) FROM approved_pickups m
LEFT JOIN pickup_points p ON p.code=m.pickup_code
WHERE p.code IS NULL;
INSERT INTO map_guard
SELECT COUNT(*) FROM approved_pickups m
LEFT JOIN legacy_orders l ON l.order_id=m.order_id
WHERE l.order_id IS NULL;
UPDATE orders o
JOIN approved_pickups m ON m.order_id=o.order_id
SET o.pickup_code=m.pickup_code;
COMMIT;
SQL

Сначала испытайте сохранённый файл в одноразовых базах штатного MySQL. trial выполняет ваш файл без изменений под изолированной учётной записью миграции, с другими историческими ID и значениями. Он сообщает фактический статус завершения SQL и фактический результат сравнения сохранности. Он никогда не меняет живые соответствия и не исправляет SQL. В плохом случае native_sql_status ненулевой, а accepted равен true только тогда, когда этот отказ ожидался и каждое рабочее значение осталось неизменным. Это означает, что проба успешно проверила отказ, а не что запись SQL прошла успешно.

./shop trial backfill.sql good
./shop trial backfill.sql missing
./shop trial backfill.sql duplicate
./shop trial backfill.sql unknown
./shop trial backfill.sql null
./shop trial backfill.sql extra

Хорошая проба применяет каждый утверждённый выбор, сохраняя более поздний явно выбранный пункт выдачи. Каждая плохая проба сообщает штатную ошибку CHECK 3819 и неизменность рабочих фактов. Эти случаи соответственно удаляют строку утверждённого соответствия, дублируют идентификатор, используют неизвестный пункт, используют NULL или пытаются перезаписать более поздний заказ. Файл, который всегда завершается ошибкой, не может пройти хорошую пробу. Если проба не принята, прочитайте её штатный stderr и причину, изучите cat backfill.sql и исправьте полный файл. Не меняйте подготовленные инструменты или соответствия, чтобы тест стал зелёным.

Примените тот же исходный файл к допустимым живым соответствиям, затем прочитайте каждый затронутый и защищённый заказ:

./shop apply backfill.sql
./shop show 101
./shop show 102
./shop show 103
./shop show 201
./shop show 202

Исторические коды — north, counter, garden. Более поздний 201 остаётся counter, а 202 — garden, с нетронутыми заметками и товарными позициями. Одной успешно созданной схемы со значениями по умолчанию недостаточно для доказательства исторических вариантов. Повтор этого заполнения истории, меняющего только данные, из нового соединения с теми же неизменными утверждёнными соответствиями даёт те же варианты; это не разрешение повторять произвольные одноразовые файлы ALTER.

При ./shopp используйте ls и исправьте имя инструмента. При синтаксической ошибке изучите и замените сохранённый SQL перед повторным применением. Подготовленный пакет останавливается при первой штатной ошибке и отключается; не используйте --force или интерактивный порядок SOURCE, продолжающий работу после неудачной защитной проверки. Это контролируемое окно обслуживания исключает одновременные изменения утверждённых данных; отдельные аудиторские запросы не являются общим протоколом блокировок для конкурентной миграции.

Ужесточите правило, сохранив оба клиента оформления

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

Продолжайте во вкладке Terminal, в /home/labex/project/pickup-desk. Утверждённое заполнение истории завершено; изучите действующее определение:

./db -e "SHOW CREATE TABLE orders;"

Столбец всё ещё допускает NULL с явным значением counter по умолчанию. NOT NULL отклоняет явный NULL; само по себе оно не даёт значения по умолчанию старому клиенту. Внешний ключ связывает pickup_code с фактическим уникальным pickup_points.code, отклоняя неизвестные коды. RESTRICT предотвращает удаление или изменение идентификатора используемого пункта. Это настоящие правила базы данных, а не проверки выпадающего списка приложения.

MODIFY COLUMN заново задаёт полное требуемое определение столбца. Сохраните и VARCHAR(16), и DEFAULT 'counter', добавляя NOT NULL; атрибуты, пропущенные в MODIFY, могут не сохраниться. Запятая помещает оба изменения в одну инструкцию ALTER. Имена ограничений идентифицируют правило, но эквивалентные имена принимаются. Держите этот DDL вне транзакции заполнения истории: его граница неявной фиксации изучена в предыдущем занятии.

cat > tighten.sql <<'SQL'
ALTER TABLE orders
MODIFY COLUMN pickup_code VARCHAR(16) NOT NULL DEFAULT 'counter',
ADD CONSTRAINT order_pickup_fk FOREIGN KEY(pickup_code)
REFERENCES pickup_points(code)
ON DELETE RESTRICT ON UPDATE RESTRICT;
SQL
./shop apply tighten.sql
./db -e "SHOW COLUMNS FROM orders;"
./db -e "SHOW CREATE TABLE orders;"

Прочитайте pickup_code как NOT NULL со значением counter по умолчанию и ссылкой на pickup_points(code) с ограниченными изменениями родительских записей. Все существующие значения остаются. Теперь создайте два разрешённых учебных заказа:

./shop old 301 Mina 1600 MUG 1
./shop new 302 Pia 1900 BAG 2 north
./shop show 301
./shop show 302

Настоящая строка старого клиента хранит counter; настоящая новая строка — north. Количество единиц товара в позиции первого заказа — 1, второго — 2; обе транзакции зафиксированы. Старый клиент всё ещё пропускает pickup_code; его не переписали скрытно для передачи значения.

Следующие два вызова — намеренные ошибки и должны завершиться ненулевым кодом. new-null передаёт настоящий SQL NULL вместо пропуска поля. Штатная ошибка 1048 должна его отклонить. Обычный новый клиент с unknown должен получить штатную ошибку внешнего ключа 1452. Они используют один и тот же свободный ID, потому что ни один отказ не должен оставить запись заказа или товарную позицию:

./shop new-null 399 Invalid 100 MUG 1
./shop new 399 Invalid 100 MUG 1 unknown
./shop show 399
./shop status

Show 399 показывает пустые header/items. Три исторических выбора, более поздние 201/202 и новые 301/302 остаются правильными. Штатная ошибка базы данных — не успешное оформление пустого заказа и не проблема соединения. Подготовленный клиент закрывает неудачную транзакцию; сброс VM не нужен. Продолжайте чтением существующих разрешённых заказов.

Итоговый результат — SQL заполнения истории и ужесточения для повторного использования плюс фактическая правильная система заказов. Проверка читает этот живой результат и испытывает исходный сохранённый SQL и текущее определение на одноразовых штатных ресурсах, включая допустимые записи и запрещённые изменения родительских данных. Она не создаёт заказ, не применяет миграцию и не исправляет отсутствующее значение в живом магазине.

Если случайно повторить успешный файл ужесточения, именованный внешний ключ уже может существовать. Изучите SHOW CREATE TABLE и сохраните допустимое определение; не удаляйте и не создавайте orders заново ради повторяемости скрипта. Устаревший файл всё равно должен содержать правильную одноразовую миграцию из указанной предшествующей фазы. Результат окна обслуживания не доказывает DDL без блокировок, произвольный откат внедрения, непрерывную доступность рабочей системы или устойчивость к подделке администратором.

Резюме

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