TSQL присоединиться к производительности - PullRequest
1 голос
/ 18 января 2012

Моя проблема в том, что этот запрос выполняется вечно:

Select
  tableA.CUSTOMER_NAME,
  tableB.CUSTOMER_NUMBER,
  TableB.RuleID
FROM tableA
INNER JOIN tableB on tableA.CUST_PO_NUMBER like tableB.CustomerMask

Вот структура таблиц:

CREATE TABLE [dbo].[TableA](
    [CUSTOMER_NAME] [varchar](100) NULL,
    [CUSTOMER_NUMBER] [varchar](50) NULL,
    [CUST_PO_NUMBER] [varchar](50) NOT NULL,
    [ORDER_NUMBER] [varchar](30) NOT NULL,
    [ORDER_TYPE] [varchar](30) NULL)

CREATE TABLE [dbo].[TableB](
    [RuleID] [varchar](50) NULL,
    [CustomerMask] [varchar](500) NULL)

TableA имеет 14 миллионов строк, а TableB имеет 1000 строк. Данные в столбце customermask могут быть такими, как «%», «ttt%», «% ttt%» и т. Д.

Как я могу настроить его, чтобы сделать его быстрее?

Спасибо!

Ответы [ 5 ]

4 голосов
/ 18 января 2012

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

Вам нужно иметь отдельное значение, по которому можно присоединиться к таблицам.Прямо сейчас он должен выполнить полное сканирование таблицы A и выполнить сопоставление подстановочных знаков для каждого элемента между Customer_Name и CustomerMask.Вы просматриваете 14 миллиардов сравнений, все из которых используют медленный оператор LIKE.

Единственное, что я могу дать, это переосмыслить архитектуру связывания правил с клиентами.

1 голос
/ 18 января 2012

Хотя вы не можете изменить то, что уже есть, вы можете создать новую таблицу следующим образом:

CREATE TABLE [dbo].[TableC](
    [CustomerMask] [varchar](500) NULL)
    [CUST_PO_NUMBER] [varchar](50) NOT NULL)

Затем создайте триггер на TableA и TableB, который вставляет / обновляет / удаляетзаписи в TableC, если они больше не соответствуют условию CUST_PO_NUMBER LIKE CustomerMask (для триггера на TableB вам нужно обновить TableC, только если поле CustomerMask было изменено.

Тогда в вашемзапрос просто станет:

SELECT 
  tableA.CUSTOMER_NAME,
  tableB.CUSTOMER_NUMBER,
  TableB.RuleID
FROM tableA
INNER JOIN tableC on tableA.CUST_PO_NUMBER = tableC.CUST_PO_NUMBER
INNER JOIN tableB on tableC.CustomerMask = tableB.CustomerMask

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

0 голосов
/ 18 января 2012

РЕДАКТИРОВАТЬ 2: Если подумать, сколько из этих масок начинаются с и и заканчиваются символами подстановки?Сначала вы можете повысить производительность:

  • Индексирование CUST_PO_NUMBER
  • Создание сохраняемого вычисляемого столбца CUST_PO_NUMBER_REV, обратного CUST_PO_NUMBER
  • Индексированиепостоянный столбец
  • Размещение статистики по этим столбцам

Тогда вы можете построить три запроса и UNION ALL результаты вместе:

SELECT ...
  FROM ...
         ON CUSTOM_PO_NUMBER LIKE CustomerMask
 WHERE /* First character of CustomerMask is not a wildcard but last one is */

UNION ALL

SELECT ...
  FROM ...
         ON CUSTOM_PO_NUMBER_REV LIKE REVERSE(CustomerMask)
 WHERE /* Last character of CustomerMask is not a wildcard but first one is */

UNION ALL

SELECT ...
  FROM ...
         ON CUSTOM_PO_NUMBER LIKE CustomerMask
 WHERE /* Everything else */

Это просто быстроНапример, вам нужно позаботиться о том, чтобы предложения WHERE давали вам взаимоисключающие результаты (или используйте UNION, но сначала стремитесь к взаимоисключающим результатам).

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

РЕДАКТИРОВАТЬ: Можно внедрить систему сегментирования для распределения таблиц клиентов и масок клиентов по нескольким серверам, а затемСервер оценивает 1/n% результатов.Вам не нужно разбивать данные - подойдет простая репликация всего содержимого каждой таблицы.Подключите серверы к вашему главному серверу, и вы можете сделать что-то с эффектом:

SELECT ... FROM OPENQUERY(LinkedServer1, 'SELECT ... LIKE ... WHERE ID BETWEEN 0 AND 99')
  UNION ALL
SELECT ... FROM OPENQUERY(LinkedServer2, 'SELECT ... LIKE ... WHERE ID BETWEEN 100 AND 199')

Примечание: OPENQUERY может быть посторонним, SQL Server может быть достаточно умен для оценки запросов на удаленномсерверы и поток результатов обратно.Я знаю, что он не делает этого для связанных серверов JET, но он мог бы лучше справляться со своим собственным видом.

Это или с помощью большего количества оборудования в проблеме.

Выможно создать индексированное представление вашего запроса для повышения производительности.

с Проектирование индексированных представлений :

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

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

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

0 голосов
/ 18 января 2012

Вы не можете использовать LIKE, если заботитесь о производительности.

Если вы пытаетесь выполнить приблизительное сопоставление строк (например, Test и est, best и т. Д.) И не хотите использовать SqlПолнотекстовый поиск взгляните на эту статью .

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

- EDIT 2--

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

Допустим, если вы настроите данные 3gram, у вас будут следующие таблицы:

Customer : 14M 
Customer3Grams : Maximum 700M //Considering the field is varchar(50)
3Grams : 78
Pattern : 1000
Pattern3Grams : 50K

Чтобы присоединить шаблон к клиенту, вам понадобится следующее соединение:

Pattern x Pattern3Grams x Customer3Grams x Customer

При соответствующей индексации (что легко) каждый поиск может происходить в O (LOG (50K) + LOG (700M) + LOG (14M)), что равно47.6.

Учитывая наличие соответствующих индексов, можно рассчитать все соединение с менее чем 50 000 просмотров и, конечно, стоимостью сканирования после поиска.Я ожидаю, что это будет очень эффективно (считанные секунды).

Стоимость создания 3грамм для каждого нового клиента также минимальна, потому что это будет максимум 50x75 возможных трех граммов, которые следует добавить в таблицу customer3Grams.

- РЕДАКТИРОВАТЬ -

В зависимости от ваших данных я также могу предложить кластеризацию на основе хеша.Я предполагаю, что номера клиентов - это числа с некоторыми шаблонами символов (например, 123231ttt3x4).Если это так, вы можете создать простую хеш-функцию, которая вычисляет результат побитового ИЛИ для каждой буквы (не цифры) и добавить его в качестве индексированного столбца в вашу таблицу.Вы можете отфильтровать результаты хэша перед применением LIKE.

В зависимости от ваших данных это может эффективно кластеризовать ваши данные и улучшить ваш поиск с учетом числа кластеров (количества хэшей).Вы можете проверить это, применив хеш и посчитав количество различных сгенерированных хешей.

0 голосов
/ 18 января 2012

Я что-то упустил?Как насчет следующего:

Select
  tableA.CUSTOMER_NAME,
  tableA.CUSTOMER_NUMBER,
  tableB.RuleID
FROM tableA, tableB 
WHERE tableA.CUST_PO_NUMBER = tableB.CustomerMask
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...