Как ускорить запрос SQL Server, включающий счетчик (different ()) - PullRequest
4 голосов
/ 12 января 2010

У меня обманчиво простой запрос к SQL Server, который занимает намного больше времени, чем я ожидал.

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT COUNT(DISTINCT(guid)) FROM listens WHERE url='http://www.sample.com/'

'guid' - это varchar (64) NULL

'url' - varchar (900) NULL

Есть указатель на guid и url.

В таблице «прослушиваний» содержится более 7 миллионов строк, из которых 17 000 соответствуют URL-адресу, о котором идет речь, а результат запроса - 5500.

Выполнение этого запроса на SQL Server 2008 занимает более 1 минуты на довольно простом двухъядерном процессоре AMD Opteron 2 ГГц с 1 ГБ ОЗУ.

Есть идеи, как сократить время выполнения? В идеале это должно быть менее 1 секунды!

Ответы [ 7 ]

5 голосов
/ 13 января 2010

Создать индекс по URL, который бы охватывал GUID:

CREATE INDEX ix_listens_url__guid ON listens (url) INCLUDE (guid)

При работе с URL-адресами в качестве идентификаторов гораздо лучше хранить и индексировать хэш URL, а не весь URL.

2 голосов
/ 02 марта 2012

Я знаю, что этот пост немного опоздал. Я искал другую проблему оптимизации.

отмечая, что:

  1. guid - это VARCHAR (64) **, а не 16-байтовый уникальный идентификатор
  2. URL-адрес varchar (900), и у вас есть 7 миллионов строк.

Мое предложение:

  1. Создать новое поле для таблицы. Column = URLHash AS UNIQUEIDENTIFIER на создание новой записи. URLHash = CONVERT( UNIQUEIDENTIFIER, HASHBYTES('MD5', url) )
  2. Создание индекса по URLHash

тогда в вашем запросе: SELECT COUNT(DISTINCT(guid)) FROM listens WHERE URLHash = CONVERT( UNIQUEIDENTIFIER, HASHBYTES( 'MD5', 'http://www.sample.com/' ) )

Это даст вам очень быстрый метод уникального поиска определенного URL при сохранении очень маленького размера индекса.

Если вам нужна ДАЛЬНЕЙШАЯ оптимизация, вы можете захотеть сделать тот же хеш для guid. Выполнение уникального 16-байтового уникального идентификатора выполняется быстрее, чем varchar (64).


Вышеупомянутое предположение состоит в том, что вы не добавляете ALOT новых строк в таблицу прослушивания; то есть новые рекордные показатели не так уж тяжелы. Причина в том, что алгоритм MD5 хотя и обеспечивает идеальную дисперсию; общеизвестно медленно. Если вы добавляете новые записи в тысячах в секунду; тогда вычисление хеша MD5 при создании записи может замедлить работу вашего сервера (если у вас нет очень быстрого сервера). Альтернативный подход заключается в реализации собственной версии алгоритма хеширования FNV1a, который не является встроенным. FNV1a намного быстрее по сравнению с MD5 и все же обеспечивает очень хорошую дисперсию / низкую частоту столкновений.

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

2 голосов
/ 13 января 2010

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

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

CREATE TABLE MyTable
(
    ID INT IDENTITY(1,1) PRIMARY KEY,
    [Guid] varchar(64),
    Url varchar(900),
    GuidChecksum int,
    UrlChecksum int
)
GO

CREATE TRIGGER trgMyTableCheckSumCalculation ON MyTable
FOR UPDATE, INSERT
as
UPDATE t1
SET    GuidChecksum = checksum(I.[Guid]),
       UrlChecksum = checksum(I.Url)
FROM   MyTable t1 
       join inserted I on t1.ID = I.ID

GO
CREATE NONCLUSTERED INDEX NCI_MyTable_GuidChecksum ON MyTable(GuidChecksum)
CREATE NONCLUSTERED INDEX NCI_MyTable_UrlChecksum ON MyTable(UrlChecksum)

INSERT INTO MyTable([Guid], Url)
select NEWID(), 'my url 1' union all
select NEWID(), 'my url 2' union all
select null, 'my url 3' union all
select null, 'my url 4'

SELECT  *
FROM    MyTable

SELECT  COUNT(GuidChecksum)
FROM    MyTable
WHERE   Url = 'my url 3'
GO

DROP TABLE MyTable
0 голосов
/ 13 января 2010

Ваш лучший возможный план - это поиск диапазона, чтобы получить URL-адреса 17 тыс. Кандидатов и число, отличное, чтобы полагаться на гарантированный порядок ввода, поэтому сортировка не требуется. Надлежащая структура данных, которая может удовлетворить оба эти требования, - это индекс (url, guid):

CREATE INDEX idxListensURLGuid on listens(url, guid);

Вы уже получили много отзывов о ширине используемого ключа, и вы можете определенно стремиться улучшить их, а также увеличить этот маленький 1 ГБ ОЗУ, если можете.

Если возможно развертывание в SQL 2008 EE, убедитесь, что вы включили сжатие страниц для такого очень повторяющегося и широкого индекса. Это сделает чудеса на производительность из-за уменьшенного ввода-вывода.

0 голосов
/ 13 января 2010

Бьюсь об заклад, если у вас более 1 ГБ памяти на машине, она будет работать лучше (все администраторы, с которыми я встречался, ожидают по крайней мере 4 ГБ на рабочем SQL-сервере.)

Я понятия не имею, имеет ли это значение, но если вы делаете

  SELECT DISTINCT(guid) FROM listens WHERE url='http://www.sample.com/'

не будет @rowcount содержать желаемый результат?

0 голосов
/ 13 января 2010

Некоторые подсказки ...

1) Рефакторинг вашего запроса, например, используйте with предложение ...

    with url_entries as (  
      select guid   
      from listens   
      where url='http://www.sample.com/'  
    )   
    select count(distinct(enries.guid)) as distinct_guid_count   
    from url_entries entries

2) Укажите точный SQL Serever, какой индекс нужно сканировать при выполнении запроса (конечно, индекс по полю url) Другой способ - просто удалить индекс на guid и оставить индекс на url в покое. Смотрите здесь для получения дополнительной информации о подсказках. Специально для конструкций типа select ... from listens with (index(index_name_for_url_field) )

3) Проверка состояния индексов в таблице listens и обновление статистики индексов .

0 голосов
/ 13 января 2010

Ваш столбец GUID по своей природе будет намного более трудоемким, чем, скажем, bigint, поскольку он занимает больше места ( 16 байт ). Вы можете заменить столбец GUID числовым столбцом с автоинкрементом или, если это не так, ввести новый столбец типа bigint / int, который увеличивается для каждого нового значения столбца GUID используйте GUID для обеспечения глобальной уникальности и bigint/in t для целей индексации)?

По ссылке выше:

16 байтов, уникальный идентификатор данных тип относительно большой по сравнению с другие типы данных, такие как 4-байтовые целые числа. Это означает, что индексы построены использование ключей уникального идентификатора может быть относительно медленнее, чем реализация индексы с использованием ключа int.

Есть ли какая-то конкретная причина, по которой вы используете varchar для столбца guid вместо uniqueidentifier?

...