Снижение производительности при переходе от кластерного составного ключа к последовательному целочисленному кластерному ключу - PullRequest
0 голосов
/ 27 сентября 2018

У меня есть таблица с первичным ключом (primkey), состоящая из (USER_NAME VARCHAR (50), AGE INT, DATE DATETIME) .Затем он индексирует кучу данных для пользователя в этот конкретный день.В общем, я бы запросил все данные для USER_NAME .

Исправьте меня, если я ошибаюсь - кластеризация здесь работает хорошо, поскольку сначала она будет кластеризована на основе USER_NAME поэтому выложу все данные для USER_NAME = JOHN_SMITH физически близко друг к другу.Затем он будет кластеризован на основе AGE и т. Д. Поскольку я запрашиваю все данные для конкретного пользователя, это должно означать, что IO оптимизирован, то есть я читаю наименьшее количество страниц и запрос, который запрашивает большой объем данных.и поэтому IO-ограничение является самым быстрым.

В настоящее время я планирую заменить (USER_NAME, AGE) на uid, который при последовательном увеличении числа является случайным отображением между (USER_NAME, AGE) и идентификатор пользователя.Это, конечно, также изменит значение первичного ключа на (UID INT, DATE DATETIME) Поскольку uid - это просто число, например (JOHN_SMITH, 24) может быть 123124 и (JOHN_SMITH, 25) может оказаться 352431, насколько я вижу, кластеризация становится бессмысленной.Я имею в виду, что хотя (JOHN_SMITH, 24) и (JOHN_SMITH, 25) в старом primkey явно являются данными для одного и того же пользователя в течение 2 последовательных лет, и БД будет кластеризоватьданные близко друг к другу на диске, номера 123124 и 352431 не содержат никакой информации о ссылочных данных.То есть старый primkey имел структуру, новый не имеет структуры и неявной информации о ссылочных данных.

Одним из решений является внедрение какой-либо схемы адресации в UID (например, стиль IPv4, нонамного проще) - каждый USER_NAME получает зарезервированное пространство из 150 UID, то есть если JOHN_SMITH получает UID 0, JOHN_SM Y TH получит UID не менее 150 и 0-149 зарезервировано для (USER_NAME = JOHN_SMITH, AGE =?) комбинации.

Я физически не хочу идти по схеме адресации.Любые мысли по этому поводу (в том числе, если моя теория верна) будут оценены.

  1. Я ограничиваю производительность в SELECT, меня не волнует INSERT и DELETE.
  2. Таблица Users очень велика (десятки ГБ).

РЕДАКТИРОВАТЬ: Пример запроса SELECT (значения, скорее всего, будут намного длиннее списка, а не только 2 элемента.

     DECLARE @testtable TABLE 
     (
     uid INT,
     startdate DATETIME,
     enddate DATETIME
     );
     INSERT INTO @testtable
     (
     uid,
     startdate,
     enddate
     )
     VALUES
     (1233890,'01-Jul-2017 00:00:00','15-Jul-2017 23:59:59'),
     (1523420,'01-Jul-2018 00:00:00','15-Jul-2018 23:59:59')

     SELECT UID, [DATE], [WAKEUP_TIME] 
     FROM dbo.USERS user 
     INNER JOIN @testtable cont 
     ON user.uid = cont.uid 
     AND user.DATE >= cont.startdate 
     AND user.DATE <= cont.enddate
     WHERE user.USER_NAME = 'John'
     ORDER BY 2 ; 

Ответы [ 2 ]

0 голосов
/ 27 сентября 2018

Вы, похоже, фильтруете по равенству на USER_NAME,AGE и по диапазону на DATE.Если вы замените USER_NAME,AGE новым искусственным значением uid, тогда поиск индекса на основе фильтрации по равенству все равно будет работать.

Из опубликованного вами запроса кажется, что SQL Server, скорее всего, выполнит его, неоднократно проверяяв USERS.Один раз за каждый элемент в @testtable.Это делается как соединение с вложенным циклом.

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

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

Самым простым решением, конечно, является не принимать новый столбец uid.Но я предполагаю, что у вас есть причины для этого.

Вы можете реализовать простую «схему адресации», избыточно упаковав значение AGE в последний байт (например, db_uid = sequential_id_for_user_name * 256 + AGE).Вы должны быть осторожны, чтобы не переполниться.

Это физически объединит связанные значения AGE и может привести к ускорению.

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

0 голосов
/ 27 сентября 2018

Во-первых, потери, о которых вы спекулируете для select запросов, - это прибыль для insert с и delete с.Новые записи просто добавляются в «конец» таблицы без разбиения страницы.

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

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

...