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

10

У меня есть таблица в SQL Server 2014, которая выглядит следующим образом:

CREATE TABLE dbo.MyTable
(
[id1] [bigint] NOT NULL,
[id2] [bigint] NOT NULL,
[col1] [int] NOT NULL default(0),
[col2] [int] NOT NULL default(0)
)

где (id1, id2) является PK. По сути, id1 - это идентификатор для группировки набора результатов (id2, col1, col2), чей pk равен id2.

Я пытаюсь использовать таблицу в памяти, чтобы избавиться от существующей дисковой таблицы, которая является моим узким местом.

  • Данные в таблице записываются -> читаются -> удаляются один раз.
  • Каждое значение id1 имеет несколько (десятки / сотни) тысяч id2.
  • Данные хранятся в таблице в течение очень короткого промежутка времени, например, 20 секунд.

Запросы, выполненные в этой таблице, следующие:

-- INSERT (can vary from 10s to 10,000s of records):
INSERT INTO MyTable
  SELECT @fixedValue, id2, col1, col2 FROM AnotherTable

-- READ:
SELECT id2, col1
FROM MyTable INNER JOIN OtherTbl ON MyTable.id2 = OtherTbl.pk
WHERE id1 = @value
ORDER BY col1

-- DELETE:
DELETE FROM MyTable WHERE id1 = @value

Вот текущее определение, которое я использовал для таблицы:

CREATE TABLE dbo.SearchItems
(
  [id1] [bigint] NOT NULL,
  [id2] [bigint] NOT NULL,
  [col1] [int] NOT NULL default(0),
  [col2] [int] NOT NULL default(0)

  CONSTRAINT PK_Mem PRIMARY KEY NONCLUSTERED (id1,id2),
  INDEX idx_Mem HASH (id1,id2) WITH (BUCKET_COUNT = 131072)
) WITH (MEMORY_OPTIMIZED = ON, DURABILITY = SCHEMA_ONLY)

К сожалению, это определение приводит к снижению производительности по сравнению с предыдущей ситуацией с таблицей на основе диска. Порядок величины более или менее на 10% выше (что в некоторых случаях достигает 100%, поэтому удваивается).

Более того, я ожидал получить супер-преимущество в сценариях с высоким уровнем параллелизма, учитывая архитектуру без блокировок, рекламируемую Microsoft. Вместо этого наихудшие показатели происходят именно тогда, когда несколько таблиц одновременно выполняют несколько запросов к таблице.

Вопросов:

  • какой правильный BUCKET_COUNT установить?
  • какой индекс я должен использовать?
  • почему производительность хуже, чем с дисковой таблицей?

Запрос sys.dm_db_xtp_hash_index_stats возвращает:

total_bucket_count = сто тридцать одна тысяча семьдесят два
empty_bucket_count = 0
avg_chain_len = 873
max_chain_length = 1009

Я изменил количество сегментов, поэтому вывод sys.dm_db_xtp_hash_index_stats :

total_bucket_count = 134217728
empty_bucket_count = 131664087
avg_chain_len = 1
max_chain_length = 3

Тем не менее, результаты почти такие же, если не хуже.

Криштиану Герси
источник
Вы уверены, что не сталкиваетесь с анализом параметров? Вы пытались выполнить запросы с помощью OPTION(OPTIMIZE FOR UNKNOWN)(см. Советы по таблице )?
ТТ.
Я думаю, вы столкнулись с проблемами цепочки строк. Можете ли вы дать нам вывод select * from sys.dm_db_xtp_hash_index_stats ? Кроме того, эта ссылка должна ответить на большинство / все ваши вопросы: msdn.microsoft.com/en-us/library/…
Шон Галларди
4
Индекс хеша полезен только для предикатов в обоих включенных столбцах. Вы пробовали без хеш-индекса на столе?
Микаэль Эрикссон
Я обнаружил, что наилучшие улучшения производительности при использовании технологии in-memory могут быть достигнуты только при использовании встроенных хранимых процедур .
Даниэль Хутмахер
@DanielHutmacher FWIW Я встречал контрпримеры, в которых все преимущества заключались в удалении блокировки и добавлении процедур, скомпилированных в оригинале, которые давали нулевое или незначительное улучшение. Я не думаю, что есть место для общего заявления (хотя вы можете быть правы в этом случае, я даже не смотрел на детали).
Аарон Бертран

Ответы:

7

Хотя этот пост не будет полным ответом из-за недостатка информации, он должен быть в состоянии указать вам правильное направление или иным образом получить представление, которым вы впоследствии сможете поделиться с сообществом.

К сожалению, это определение приводит к снижению производительности по сравнению с предыдущей ситуацией с таблицей на основе диска. Порядок величины более или менее на 10% выше (что в некоторых случаях достигает 100%, поэтому удваивается).

Более того, я ожидал получить супер-преимущество в сценариях с высоким уровнем параллелизма, учитывая архитектуру без блокировок, рекламируемую Microsoft. Вместо этого наихудшие показатели происходят именно тогда, когда несколько таблиц одновременно выполняют несколько запросов к таблице.

Это беспокоит, поскольку это определенно не должно иметь место. Определенные рабочие нагрузки не предназначены для таблиц памяти (SQL 2014), и некоторые рабочие нагрузки поддаются ему. В большинстве ситуаций может быть минимальное снижение производительности, просто путем миграции и выбора правильных индексов.

Первоначально я думал очень узко о ваших вопросах относительно этого:

Вопросов:

  • какой правильный BUCKET_COUNT установить?
  • какой индекс я должен использовать?
  • почему производительность хуже, чем с дисковой таблицей?

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

-- INSERT (can vary from 10s to 10,000s of records):
INSERT INTO MyTable
  SELECT @fixedValue, id2, col1, col2 FROM AnotherTable

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

Когда я провел быструю проверку на 100 000 вставок строк из таблицы на основе диска после загрузки данных в память - это было время отклика менее секунды. Однако большая часть ваших данных хранится только в течение очень короткого промежутка времени, менее 20 секунд. Это не дает много времени, чтобы действительно жить в кеше. Кроме того, я не уверен, насколько велик на AnotherTableсамом деле, и не знаю, считываются ли значения с диска или нет. Мы должны положиться на вас за эти ответы.

С помощью запроса Выбрать:

SELECT id2, col1
FROM MyTable INNER JOIN OtherTbl ON MyTable.id2 = OtherTbl.pk
WHERE id1 = @value
ORDER BY col1

Опять же, мы во власти производительности таблиц interop + disk. Кроме того, сортировки недешевы для индексов HASH, поэтому следует использовать некластеризованный индекс. Об этом говорится в руководстве по индексам, которое я привел в комментариях

Чтобы привести некоторые фактические факты, основанные на исследованиях, я загрузил SearchItemsв память таблицу с 10 миллионами строк и AnotherTable100 000, поскольку я не знал ее фактического размера или статистики. Затем я использовал запрос select для выполнения. Кроме того, я создал расширенный сеанс событий в wait_completed и поместил его в кольцевой буфер. Это было убрано после каждого запуска. Я также побежал DBCC DROPCLEANBUFFERSимитировать среду, в которой все данные могут быть не резидентными.

Результаты не были чем-то впечатляющим, если смотреть на них в вакууме. Так как ноутбук, на котором я тестирую этот компьютер, использует SSD более высокого класса, я искусственно уменьшил производительность диска для используемой виртуальной машины.

Результаты были получены без информации ожидания после 5 запусков запроса только по таблице в памяти (без объединения и без подзапросов). Это в значительной степени, как и ожидалось.

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

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

Наконец запрос на удаление. Поиск строк, основанных только на ID1, не очень эффективен с индексом has. Хотя верно, что предикаты равенства - это то, для чего нужны хеш-индексы, область, в которую попадают данные, основана на целых хэшированных столбцах. Таким образом, id1, id2, где id1 = 1, id2 = 2 и id1 = 1, id2 = 3, будут хэшироваться в разные сегменты, поскольку хэш будет проходить через (1,2) и (1,3). Это не будет простым сканированием диапазона B-Tree, поскольку хеш-индексы структурированы не одинаково. Тогда я ожидал бы, что это не будет идеальным показателем для этой операции, однако я бы не ожидал, что это займет на несколько порядков больше, чем у опытных. Мне было бы интересно увидеть wait_info по этому вопросу.

Более того, я ожидал получить супер-преимущество в сценариях с высоким уровнем параллелизма, учитывая архитектуру без блокировок, рекламируемую Microsoft. Вместо этого наихудшие показатели происходят именно тогда, когда несколько таблиц одновременно выполняют несколько запросов к таблице.

Хотя верно, что блокировки используются для логической согласованности, операции все еще должны быть атомарными. Это делается с помощью специального оператора сравнения на основе ЦП (именно поэтому In-Memory работает только с некоторыми [хотя и почти всеми процессорами, изготовленными за последние 4 года]). Таким образом, мы не получаем все бесплатно, еще будет время для выполнения этих операций.

Еще один момент, о котором следует упомянуть, - это тот факт, что почти во всех запросах используется интерфейс T-SQL (а не компилируемый в естественном порядке SPROC), который затрагивает хотя бы одну таблицу на основе дисков. Вот почему я считаю, что, в конце концов, у нас фактически нет увеличения производительности, поскольку мы все еще ограничены производительностью таблиц на основе дисков.

Следовать за:

  1. Создайте расширенный сеанс событий для wait_completed и укажите известный вам SPID. Запустите запрос и дайте нам вывод или используйте его для внутреннего использования.

  2. Дайте нам обновленную информацию о выходе из # 1.

  3. Не существует магического числа для определения количества сегментов для хеш-индексов. В основном, до тех пор, пока сегменты не будут полностью заполнены, а цепочки рядов останутся ниже 3 или 4, производительность должна оставаться приемлемой. Это все равно, что спросить: «На что мне установить файл журнала?» - это будет зависеть от процесса, базы данных, типа использования.

Шон Галларди
источник