QA Interview Hub · База знань · Тести

SQL

Зміна даних і транзакції

INSERT, UPDATE, DELETE, ACID, ізоляція

Як додати дані через INSERT?

Перелічуй стовпці явно — тоді запит не зламається, якщо в таблицю додадуть нові. Можна вставити кілька рядків за раз. У PostgreSQL RETURNING одразу повертає створені значення, наприклад згенерований id.

INSERT INTO users (email, name, country)
VALUES ('qa_1@test.com', 'QA One', 'PL'),
       ('qa_2@test.com', 'QA Two', 'UA')
RETURNING id, email;

-- вставка результату іншого запиту
INSERT INTO accounts (user_id, currency, balance)
SELECT id, 'PLN', 0 FROM users WHERE email LIKE 'qa_%@test.com';
На співбесіді: INSERT додає рядки; я завжди перелічую стовпці явно, щоб запит не зламався від змін у таблиці, і можу вставити кілька рядків за раз. У PostgreSQL RETURNING одразу повертає згенерований id, що зручно для підготовки тестових даних: створив користувача і одразу маю його id для наступних кроків.

Як безпечно виконувати UPDATE і DELETE?

Головне правило: UPDATE і DELETE без WHERE змінюють усю таблицю. Звичка, що рятує:

  1. Спочатку написати SELECT з тим самим WHERE і подивитися, які й скільки рядків потраплять під зміну.
  2. Відкрити транзакцію.
  3. Виконати зміну й перевірити кількість змінених рядків.
  4. COMMIT — якщо все як очікувалося, ROLLBACK — якщо ні.
SELECT id, status FROM accounts WHERE user_id = 42;   -- 1. що зачепимо

BEGIN;                                                -- 2.
UPDATE accounts SET status = 'blocked' WHERE user_id = 42;
-- UPDATE 2   <- очікували 2 рахунки                    3.
COMMIT;                                               -- 4. або ROLLBACK;
На співбесіді: UPDATE і DELETE без WHERE змінюють усю таблицю, тому в мене залізна звичка: спочатку SELECT з тим самим WHERE, щоб побачити, які рядки зачеплю, потім зміна в транзакції, перевірка кількості змінених рядків і лише тоді COMMIT. Якщо цифра не та — ROLLBACK.

Чим відрізняються DELETE, TRUNCATE і DROP?

DELETETRUNCATEDROP
Що видаляєРядки (можна з WHERE)Усі рядкиТаблицю повністю зі структурою
WHEREТакНіНі
ШвидкістьПовільно, по рядкуШвидкоШвидко
Тригери на рядкиСпрацьовуютьНіНі
ВідкатУ транзакції — такЗалежить від СУБД: у PostgreSQL так, у MySQL ніЗалежить від СУБД
Лічильник idНе скидаєтьсяMySQL скидає; PostgreSQL — лише з RESTART IDENTITYЗникає разом з таблицею
На співбесіді: DELETE видаляє рядки за умовою, працює по рядку, запускає тригери і відкочується в транзакції. TRUNCATE швидко видаляє всі рядки без умови, і чи можна його відкотити, залежить від СУБД. DROP видаляє таблицю разом зі структурою. Для очищення тестових даних я використовую DELETE з точним фільтром, а TRUNCATE — лише на власному локальному середовищі.

Що таке транзакція і як QA її використовує?

Транзакція — група операцій, які виконуються як одне ціле: або всі, або жодна. Переказ — це списання з одного рахунку і зарахування на інший; якщо друге впало, перше теж має відкотитися.

  • BEGIN — почати
  • COMMIT — зафіксувати зміни
  • ROLLBACK — скасувати все з початку транзакції

Як QA використовує: безпечно змінювати дані вручну (є шлях назад); підготувати стан для перевірки і відкотити після; тестувати, що застосунок не лишає «половинчастих» операцій при збої.

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 10;
UPDATE accounts SET balance = balance + 100 WHERE id = 11;
-- перевіряю, що обидва баланси правильні
SELECT id, balance FROM accounts WHERE id IN (10, 11);
COMMIT;   -- або ROLLBACK, якщо щось не так
На співбесіді: Транзакція — це група операцій, що виконуються як одне ціле: або всі, або жодна. Класичний приклад — переказ: списання і зарахування мають пройти разом. Як QA я використовую транзакції, щоб безпечно змінювати дані з можливістю відкату, і тестую, що застосунок не лишає половинчастих операцій, якщо на середині стається збій.

Що таке ACID?

  • Atomicity (атомарність) — транзакція виконується повністю або не виконується зовсім.
  • Consistency (узгодженість) — транзакція переводить базу з одного коректного стану в інший, не порушуючи обмежень: ключів, CHECK, унікальності.
  • Isolation (ізольованість) — паралельні транзакції не бачать проміжних станів одна одної (ступінь залежить від рівня ізоляції).
  • Durability (довговічність) — після COMMIT дані не зникнуть навіть при збої сервера.
На співбесіді: ACID — чотири гарантії транзакцій. Атомарність — все або нічого. Узгодженість — база переходить між коректними станами без порушення обмежень. Ізольованість — паралельні транзакції не бачать проміжних станів одна одної. Довговічність — зафіксоване після COMMIT не зникне навіть при збої. На прикладі переказу: гроші не можуть списатися без зарахування, і два паралельні перекази не можуть обидва витратити той самий залишок.

Що таке рівні ізоляції і які проблеми паралельного доступу вони вирішують?

Аномалії, що виникають, коли транзакції працюють одночасно:

  • Dirty read — читання незафіксованих змін іншої транзакції, які потім відкотять.
  • Non-repeatable read — повторне читання того самого рядка в межах транзакції дає інше значення.
  • Phantom read — повторний запит з тим самим фільтром повертає нові рядки.
  • Lost update — дві транзакції читають баланс 100, обидві віднімають 80 і записують 20. Одне списання «загубилося».

Рівні від слабшого до сильнішого: Read Uncommitted, Read Committed, Repeatable Read, Serializable. За замовчуванням у PostgreSQL — Read Committed, у MySQL InnoDB — Repeatable Read.

Захист від lost update: атомарний UPDATE … SET balance = balance - 80 WHERE balance >= 80, блокування рядка SELECT … FOR UPDATE або вищий рівень ізоляції.

BEGIN;
SELECT balance FROM accounts WHERE id = 10 FOR UPDATE;  -- блокує рядок
-- друга транзакція з FOR UPDATE чекатиме тут
UPDATE accounts SET balance = balance - 80 WHERE id = 10;
COMMIT;
На співбесіді: Коли транзакції працюють одночасно, бувають аномалії: брудне читання незафіксованих даних, неповторюване читання, фантомні рядки і втрачене оновлення. Рівні ізоляції — від Read Uncommitted до Serializable — по-різному від них захищають; у PostgreSQL за замовчуванням Read Committed. Як QA я тестую це паралельними запитами: наприклад, два одночасні списання з одного рахунку не повинні дати від'ємного балансу.

Як безпечно змінювати дані на спільному тестовому середовищі?

  • Працювати лише зі своїми даними: помічати їх маркером (префікс email qa_, тег, окремий тенант) і фільтрувати за ним.
  • Спочатку SELECT, потім зміна в транзакції, перевірка кількості рядків.
  • Не чіпати дані, від яких залежать колеги чи автотести; якщо треба — попередити в каналі команди.
  • Зберігати скрипти підготовки й очищення, щоб їх можна було повторити.
  • Ніколи не виконувати зміни на проді. Доступ до проду — лише читання і лише якщо дозволено.
На співбесіді: На спільному середовищі я змінюю лише власні дані, позначені маркером, наприклад префіксом в email, і фільтрую за ним. Спершу SELECT, потім зміна в транзакції з перевіркою кількості рядків. Скрипти підготовки й очищення зберігаю, щоб їх можна було повторити, і попереджаю команду, якщо зачіпаю спільні дані.

DELETE vs TRUNCATE vs DROP і обережність

DELETE видаляє рядки за умовою WHERE, записує кожну операцію в лог, запускає тригери й може бути відкочений у транзакції. TRUNCATE швидко очищає всю таблицю без умов і зазвичай скидає лічильник автоінкременту. DROP видаляє саму таблицю разом зі структурою, індексами і даними.

На спільному середовищі одна помилка зачіпає всю команду: DELETE чи UPDATE без WHERE змінить усі рядки. Тому спершу пишу SELECT з тією самою умовою й перевіряю, які саме рядки будуть змінені. Видаляю лише свої тестові дані, бажано позначені префіксом чи окремим акаунтом.

Транзакції допомагають: BEGIN, потім зміна, перевірка результату і ROLLBACK, якщо щось не так, або COMMIT, якщо все правильно. Варто пам'ятати, що в PostgreSQL TRUNCATE можна відкотити в транзакції, а в MySQL — ні, бо там це DDL-команда з неявним COMMIT. На продакшні без узгодження й бекапу не змінюю нічого.