Минимизация индексированных операций чтения со сложными критериями


12

Я оптимизирую базу данных рабочих билетов Firebird 2.5. Они хранятся в таблице, объявленной так:

CREATE TABLE TICKETS (
  TICKET_ID id PRIMARY KEY,
  JOB_ID id,
  ACTION_ID id,
  STATUS str256 DEFAULT 'Pending'
);

Я обычно хочу найти первый билет, который не был обработан и находится в Pendingстатусе.

Мой цикл обработки будет:

  1. Получить 1-й билет, где Pending
  2. Работайте с Ticket.
  3. Обновить статус заявки => Complete
  4. Повторение.

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

У меня есть индекс Status, но кажется, что он просматривает Ticket_Idстолбец каждую итерацию. Кажется, я что-то упускаю, но не уверен, что. Ожидается ли увеличение числа индексированных операций чтения для чего-то подобного этому, или индекс работает неправильно?

- Редактирует для комментариев -

В Firebird вы ограничиваете поиск строк следующим образом:

Select First 1
  Job_ID, Ticket_Id
From
  Tickets
Where
  Status = 'Pending'

Поэтому, когда я говорю «сначала», я просто спрашиваю, где ограниченный набор записей Status = 'Pending'.


Что вы имеете в виду с «первым» в «Получить 1-й билет, где« в ожидании »» ?
ypercubeᵀᴹ

Если «первое» означает наименьшее ticket_id, вам, вероятно, нужен индекс(status, ticket_id)
ypercubeᵀᴹ

И насколько вы уверены, что снижение производительности вызвано этой процедурой, а не другими запросами / утверждениями?
ypercubeᵀᴹ

@ypercube - Нет, я не уверен, что это снижение производительности. Вот почему мой вопрос был: «Мне нужно беспокоиться об этом или это нормальное поведение индекса?». Это то, что я заметил во время мониторинга базы данных, и я счел это неожиданным. Я не ожидал бы, что он продолжит сканировать предыдущие строки, когда я предоставлю предложение where для индексированного столбца. FWIW, изменение индекса для включения ticket_idфактически выполняется хуже, чем просто индексирование Status.
GDCD

Является ли id(тип данных) доменом, который вы определили?
a_horse_with_no_name

Ответы:


1

Ухудшение с течением времени происходит из-за увеличения количества элементов, которые находятся в состоянии «Завершено». Подумайте об этом на секунду - вы не получите никакого снижения производительности при тестировании, поскольку у вас, вероятно, есть небольшое количество строк со статусом «Завершено». Но в процессе производства они могут иметь миллионы строк со статусом «Завершено», и это число со временем будет увеличиваться. Это, по сути, делает ваш индекс статуса все менее и менее полезным с течением времени. Таким образом, база данных, вероятно, просто решает, что, поскольку Status почти всегда имеет значение «Complete», она будет просто сканировать таблицу вместо использования индекса.

В SQL Server (и, возможно, в других СУБД?) Это можно обойти, используя отфильтрованные индексы. В SQL Server вы бы добавили условие WHERE в конец определения вашего индекса, чтобы сказать «применять этот индекс только к записям со статусом <>« Завершено »». Тогда любой запрос, использующий этот предикат, скорее всего будет использовать индекс для небольшого количества записей, для которых не установлено значение «Завершено». Однако, основываясь на документации здесь: http://www.firebirdsql.org/refdocs/langrefupd25-ddl-index.html , похоже, что Firebird не поддерживает отфильтрованные индексы.

Обходной путь - поместить записи «Complete» в таблицу ArchiveTickets. Создайте таблицу с точно таким же определением (хотя и без какого-либо автоматически сгенерированного идентификатора), как у вашей таблицы Tickets, и поддерживайте строки между ними, помещая записи «Complete» в таблицу ArchiveTickets. Индекс на вашей таблице Билетов будет тогда с гораздо меньшим количеством записей и будет намного более эффективным. Это, вероятно, будет означать, что вам нужно будет изменить любые отчеты и т. Д., Которые ссылаются на «Завершенные» билеты, чтобы указывать на таблицу «Архив», или выполнять UNION для билетов и ArchiveTickets. Это будет иметь преимущество не только в быстроте, но и в том, что вы сможете создавать специальные индексы для таблицы ArchiveTickets, чтобы она работала лучше для других запросов (например:

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


0

Будет ли это зависеть от производительности, будет зависеть от объема данных и производительности машины. Учитывая возможности современного аппаратного обеспечения, трудно представить объем продаж билетов, который не может быть обработан описанным вами дизайном. Тем не менее, есть некоторые изменения, которые я бы порекомендовал для корректности, и которые могут улучшить производительность в качестве вторичного преимущества.

Ваш ожидающий запрос get first является недетерминированным. Сначала по какому порядку? Таблица SQL не имеет внутреннего порядка; First 1хак просто дает вам некоторую произвольную первую. Чтобы сделать его детерминированным, почему бы не обработать отложенные задания в порядке Job_ID?

Если у вас есть два индекса {Job_ID} и {Status, Job_ID}, этот запрос вернет одну строку предсказуемо и эффективно:

Select Job_ID, Ticket_Id
From   Tickets
Where Job_ID = ( 
  select min(Job_ID) from Tickets 
  where Status = 'Pending'
);

Я не пользователь Firebird, поэтому вам нужно проверить план запроса, но он должен быть эффективным, поскольку подзапрос ссылается только на второй индекс, выдает значение для первого. (Для вас могут быть доступны другие приемы повышения эффективности. Вы можете организовать физическую таблицу в виде дерева B + или, например, иметь доступ к скрытому row_id.)

Другое изменение, которое я бы сделал для корректности, - это создание Statusодного ограниченного байта и предоставление приложению строки «Pending». Это защитит от ошибочных Statusзначений и, вероятно, уменьшит индекс при сделке. Что-то вроде:

CREATE TABLE TICKETS (
  TICKET_ID id PRIMARY KEY,
  JOB_ID id,
  ACTION_ID id,
  STATUS char(1) not NULL 
     DEFAULT 'P'
     CHECK( STATUS in ('P', 'C', 'X') ) -- whatever the domain is
);

Конечно, вы можете использовать представление (или, возможно, производный столбец) для предоставления канонических строк для Status.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.