Индексы и транзакции в базе данных

Как устроен индекс на B-дереве, когда он ускоряет и когда мешает, свойства ACID, уровни изоляции транзакций, аномалии чтения, блокировки и взаимоблокировки в СУБД.

Индексы и транзакции — две темы, без которых практическая часть курсовой по базам данных выглядит поверхностной. Первая отвечает за скорость, вторая за целостность данных.

Зачем нужен индекс

Без индекса СУБД читает всю таблицу целиком — полное сканирование со сложностью O(n). Индекс на B-дереве позволяет найти нужную строку за O(log n): в таблице на миллион строк это порядка двадцати сравнений вместо миллиона.

CREATE INDEX idx_employees_dept ON employees(dept_id);
CREATE UNIQUE INDEX idx_users_email ON users(email);

-- Составной индекс: порядок столбцов имеет значение
CREATE INDEX idx_orders_customer_date ON orders(customer_id, order_date);

-- Частичный индекс: только активные записи
CREATE INDEX idx_active_users ON users(last_login) WHERE is_active = true;

-- Посмотреть, используется ли индекс
EXPLAIN ANALYZE SELECT * FROM employees WHERE dept_id = 5;
Тип индексаУстройствоДля чего
B-деревоСбалансированное деревоПо умолчанию: =, <, >, BETWEEN, ORDER BY
ХешХеш-таблицаТолько строгое равенство, зато быстрее
GiST / GINОбобщённые деревьяПолнотекстовый поиск, массивы, JSON, геоданные
Битовая картаБитовые маскиСтолбцы с малым числом значений в аналитике
КластерныйФизический порядок храненияОдин на таблицу, обычно первичный ключ
Составной индекс (a, b) работает для условий по a и по паре (a, b), но не для условия только по b. Это правило левого префикса — частая причина того, что созданный индекс не используется.

Цена индекса

Практическое правило: индексируйте внешние ключи, столбцы в WHERE и JOIN, поля сортировки. Не индексируйте всё подряд — на таблице с интенсивной записью лишние индексы обходятся дороже, чем экономят.

Транзакции и ACID

BEGIN;
  UPDATE accounts SET balance = balance - 5000 WHERE id = 1;
  UPDATE accounts SET balance = balance + 5000 WHERE id = 2;
COMMIT;

-- При ошибке между операциями:
ROLLBACK;   -- деньги не исчезнут со счёта отправителя
СвойствоРасшифровкаЧто гарантирует
AtomicityАтомарностьЛибо все операции, либо ни одной
ConsistencyСогласованностьБД переходит из одного корректного состояния в другое
IsolationИзолированностьПараллельные транзакции не мешают друг другу
DurabilityДолговечностьПосле COMMIT данные переживут отключение питания

Аномалии параллельного доступа

АномалияЧто происходит
Грязное чтениеПрочитаны данные незавершённой транзакции, которая потом откатилась
Неповторяющееся чтениеПовторный SELECT той же строки вернул другое значение
Фантомное чтениеПовторный SELECT по условию вернул новые строки
Потерянное обновлениеДве транзакции записали в одно поле, одна перезаписала другую

Уровни изоляции

УровеньГрязноеНеповторяющеесяФантомы
READ UNCOMMITTEDВозможноВозможноВозможны
READ COMMITTEDНетВозможноВозможны
REPEATABLE READНетНетВозможны
SERIALIZABLEНетНетНет
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
  SELECT balance FROM accounts WHERE id = 1;
  -- ... расчёты ...
  SELECT balance FROM accounts WHERE id = 1;   -- гарантированно то же значение
COMMIT;
Чем выше уровень изоляции, тем меньше аномалий и тем ниже параллелизм. По умолчанию PostgreSQL и Oracle используют READ COMMITTED, MySQL InnoDB — REPEATABLE READ. Переходить на SERIALIZABLE стоит только там, где цена ошибки выше цены производительности.

Блокировки

-- Явная блокировка строки до конца транзакции
BEGIN;
  SELECT * FROM accounts WHERE id = 1 FOR UPDATE;
  UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT;

-- Оптимистичный вариант без блокировки
UPDATE accounts
   SET balance = 900, version = version + 1
 WHERE id = 1 AND version = 5;
-- Изменено 0 строк — значит кто-то опередил, повторяем операцию

Взаимоблокировки в СУБД

Две транзакции блокируют строки в разном порядке и ждут друг друга. Современные СУБД обнаруживают такой цикл автоматически и снимают одну транзакцию с ошибкой deadlock detected — приложение должно её повторить.

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

Что показать в курсовой

  1. Замерить время запроса до и после создания индекса на таблице хотя бы в сто тысяч строк.
  2. Приложить вывод EXPLAIN ANALYZE с планом Seq Scan и Index Scan — наглядное доказательство эффекта.
  3. Продемонстрировать откат транзакции при нарушении ограничения целостности.
  4. Смоделировать аномалию на низком уровне изоляции и показать, как её устраняет повышение уровня.

Частые вопросы

Почему созданный индекс не используется?

Причин несколько: таблица слишком мала и планировщик выбрал сканирование, столбец обёрнут функцией, нарушено правило левого префикса составного индекса, либо не обновлена статистика — помогает ANALYZE.

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

Нет, СУБД создаёт его автоматически при объявлении PRIMARY KEY или UNIQUE. Дублирующий индекс только займёт место.

Что произойдёт при сбое питания в середине транзакции?

При перезапуске СУБД по журналу упреждающей записи откатит незавершённые транзакции и накатит подтверждённые. Именно журнал обеспечивает атомарность и долговечность.

Читайте также

Сделаем работу по этой теме

Опишите задачу — ответим в течение 15 минут в личных сообщениях ВКонтакте, назовём срок и цену. Предоплаты за оценку нет.

  • Оценка заявки бесплатно
  • Правки по замечаниям преподавателя
  • Работы по всем техническим и IT-дисциплинам

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