SQL Server медленный выбор из большой таблицы - PullRequest
9 голосов
/ 02 декабря 2009

У меня есть таблица с более чем 20 миллионами записей.

Структура как:

EventId UNIQUEIDENTIFIER
SourceUserId UNIQUEIDENTIFIER
DestinationUserId UNIQUEIDENTIFIER
CreatedAt DATETIME
TypeId INT
MetaId INT

Таблица получает около 100 тыс. + Записей каждый день.

У меня есть индексы для каждого столбца, кроме MetaId, так как он не используется в предложениях 'where'

Проблема в том, когда я хочу забрать, например. последние 100 записей для желаемого SourceUserId

Выполнение запроса иногда занимает до 4 минут, что недопустимо.

Например.

SELECT TOP 100 * FROM Events WITH (NOLOCK)
WHERE SourceUserId = '15b534b17-5a5a-415a-9fc0-7565199c3461'
AND 
(
 TypeId IN (2, 3, 4)
    OR 
 (TypeId = 60 AND SrcMemberId != DstMemberId)
)
ORDER BY CreatedAt DESC

Я не могу выполнять разбиение и т. Д., Поскольку я использую стандартную версию SQL Server, а Enterprise слишком дорогой.

Я также думаю, что стол достаточно мал, чтобы быть таким медленным.

Я думаю, что проблема в предложении ORDER BY, так как db должен проходить через гораздо больший набор данных.

Есть идеи, как сделать это быстрее?

Возможно, реляционная база данных не очень хорошая идея для таких данных.

Данные всегда собираются по заказу CreatedAt DESC

Спасибо за чтение.

PabloX

Ответы [ 9 ]

15 голосов
/ 02 декабря 2009

Скорее всего, вы захотите создать составной индекс для этого типа запроса - когда запрос выполняется медленно, он, скорее всего, выберет сканирование индекса по столбцу CreatedAt и выполнение остаточного фильтра по значению SourceUserId, когда в на самом деле, то, что вы хотите сделать, это перейти непосредственно ко всем записям для данного SourceUserId, упорядоченного должным образом - для этого вам нужно будет создать составной индекс в основном на SourceUserId (выполнение проверки на равенство) и во вторую на CreateAt (чтобы сохранить порядок в пределах заданного значения SourceUserId). Вы также можете попробовать добавить TypeId, в зависимости от селективности этого столбца.

Итак, 2, которые, скорее всего, дадут наилучшую воспроизводимую производительность (опробуйте их и сравните), будут:

  1. Индекс включен (SourceUserId, CreatedAt)
  2. Индекс включен (SourceUserId, TypeId, CreatedAt)

Как всегда, при определении того, как / что / где индексировать, следует учитывать и множество других соображений, поскольку Ремус в отдельном ответе обсуждает один важный аспект: охватывать запрос, а не вести поиск. Кроме того, вам необходимо учесть объемы записи, возможное влияние фрагментации (если есть) , одноэлементный поиск по сравнению с большими последовательными сканированиями и т. Д. И т. Д.

6 голосов
/ 02 декабря 2009

У меня есть индексы для каждого столбца, кроме MetaId

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

INDEX ON (SourceUserId , CreatedAt) INCLUDE (TypeId, SrcMemberId, DstMemberId)

Следующий индекс также полезен, хотя он все еще будет вызывать поиск:

INDEX ON (SourceUserId , CreatedAt) INCLUDE (TypeId)

и, наконец, индекс без любого включенного столбца может помочь, но, скорее всего, будет проигнорирован (зависит от статистики столбца и оценок количества элементов):

INDEX ON (SourceUserId , CreatedAt)

Но отдельный индекс для SourceUSerId и индекс для CreatedAt практически бесполезны для вашего запроса.

См. Основы расчета индекса .

5 голосов
/ 02 декабря 2009

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

  • Высокая фрагментация индекса: , поскольку новые идентификаторы GUID генерируются случайным образом, индекс не может организовать их в последовательном порядке, а узлы распределены неравномерно.
  • Большое количество разбиений страницы: размер GUID (16 байт) вызывает много разбиений страницы в индексе, поскольку существует большая вероятность, что новое значение не поместится в оставшееся доступное пространство на странице.
  • Сравнение медленных значений: Сравнение двух идентификаторов GUID является относительно медленной операцией, поскольку все 33 символа должны совпадать.

Вот пара ресурсов о том, как исследовать и решить эти проблемы:

1 голос
/ 02 декабря 2009

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

1 голос
/ 02 декабря 2009

Я предлагаю использовать СОЮЗ:

SELECT TOP 100 x.*
  FROM (SELECT a.*
          FROM EVENTS a
         WHERE a.typeid IN (2, 3, 4)
        UNION ALL
        SELECT b.*
          FROM EVENTS b
         WHERE b.typeid = 60 
           AND b.srcmemberid != b.dstmemberid) x
 WHERE x.sourceuserid = '15b534b17-5a5a-415a-9fc0-7565199c3461'
1 голос
/ 02 декабря 2009

Я бы порекомендовал получать данные в таблицах 2 sep var

INSERT INTO @Table1
SELECT * FROM Events WITH (NOLOCK)
WHERE SourceUserId = '15b534b17-5a5a-415a-9fc0-7565199c3461'
AND 
(
 TypeId IN (2, 3, 4)
)
INSERT INTO @Table2
SELECT * FROM Events WITH (NOLOCK)
WHERE SourceUserId = '15b534b17-5a5a-415a-9fc0-7565199c3461'
AND 
(
 (TypeId = 60 AND SrcMemberId != DstMemberId)
)

затем примените unoin из выбора, упорядоченного и верхнего. Ограничьте данные с самого начала.

0 голосов
/ 02 декабря 2009

Если ежедневно добавляется 100 тыс. Записей, вам следует проверить фрагментацию индекса. И перестроить или реорганизовать его соответственно. Больше информации : SQLauthority

0 голосов
/ 02 декабря 2009

Вы можете разделить запрос на две части с помощью UNION, чтобы избежать ИЛИ (что может привести к тому, что ваш индекс не будет использоваться), что-то вроде

   SElect * FROM(
 SELECT TOP 100 * FROM Events WITH (NOLOCK)
WHERE SourceUserId = '15b534b17-5a5a-415a-9fc0-7565199c3461'
AND TypeId IN (2, 3, 4)
UNION  SELECT TOP 100 * FROM Events WITH (NOLOCK)
WHERE SourceUserId = '15b534b17-5a5a-415a-9fc0-7565199c3461' 
 AND TypeId = 60 AND SrcMemberId != DstMemberId
)
ORDER BY CreatedAt DESC

Кроме того, убедитесь, что индексы uniqueidentifier не CLUSTERED.

0 голосов
/ 02 декабря 2009

Я бы удостоверился, что CreatedAt правильно проиндексирован

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...