Поиск по 13 миллионам записей с использованием полнотекстового поиска с дополнительными условиями

Проблема с производительностью при выполнении полнотекстового поиска SQL Server с дополнительными условиями. (SQL сервер 2012)

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

Индекс полнотекстового поиска уже есть в таблице Names для столбца SNAME.

В хранимой процедуре параметр табличного типа SearchFilter используется для передачи списка информации об имени и адресе.

Обе таблицы имеют более 14 миллионов записей, когда мы выполняем процедуру с 1000 уникальными записями, переданными в списке фильтров, для возврата результата (1400 записей) потребовалось около 7 минут.

Критерии фильтрации: содержит (имя) и почтовый адрес, город, штат, почтовый индекс точное совпадение.

Есть ли альтернатива, чтобы избежать цикла while, поскольку функция SQL Server CONTAINS требует строкового значения или переменной?

CREATE TABLE [dbo].[Names]
(
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [UIN] [varchar](9) NULL,
    [SNAME] [varchar](500) NULL,
    CONSTRAINT [PK_Names] 
        PRIMARY KEY CLUSTERED ([ID] ASC)
)

CREATE TABLE [dbo].[ADDRESSES]
(
    [UIN] [varchar](9) NULL,
    [STREET1] [varchar](100) NULL,
    [STREET2] [varchar](50) NULL,
    [CITY] [varchar](30) NULL,
    [STATE] [varchar](2) NULL,
    [ZIP] [varchar](10) NULL    
) ON [PRIMARY]

CREATE TYPE [dbo].[SearchFilter] AS TABLE
(
    [UIN] [varchar](40) NULL,
    [SNAME] [varchar](max) NULL,
    [StreetAddress] [varchar](max) NULL,
    [City] [varchar](max) NULL,
    [State] [varchar](50) NULL,
    [Zip] [varchar](20) NULL
)

-- Stored procedure logic
DECLARE @filterList AS [dbo].[SearchFilter]

DECLARE @NoOfRows INT, @counter INT = 0

SET @NoOfRows = (SELECT COUNT(1) FROM @filterList)

DECLARE @result TABLE (UIN varchar(40), 
                       NAME varchar(500), 
                       StreetAddress varchar(1000), 
                       Zipcode varchar(20),
                       State varchar(20),
                       City varchar(1000),
                       IsRecordFound varchar(50)
                      );

WHILE (@NoOfRows > @counter)
BEGIN
    DECLARE @SearchName VARCHAR(4000)

    SET @SearchName = (SELECT '"'+SNAME+'"' FROM @filterList ORDER BY SNAME OFFSET @counter ROWS FETCH NEXT 1 ROWS ONLY)  

    --Start: Process to Select Records
    ;WITH Filter_CTE AS
    (
        SELECT 
            SNAME, StreetAddress, City, State, ZipCode 
        FROM
            @filterList 
        ORDER BY 
            SNAME 
            OFFSET @counter ROWS FETCH NEXT 1 ROWS ONLY 
    )
    INSERT INTO @result (UIN, NAME, STREETADDRESS, CITY, STATE, ZIPCODE, PHONE, IsRecordFound)
        SELECT DISTINCT 
            en.UIN, ISNULL(en.SNAME, Filter_CTE.SNAME),
            Filter_CTE.StreetAddress, Filter_CTE.ZipCode,
            Filter_CTE.state, Filter_CTE.City,
            IIF(en.UIN IS NULL, 'Not Found', 'Found') AS IsRecordFound 
        FROM 
            dbo.Names en 
        INNER JOIN 
            dbo.ADDRESSES ea ON en.UIN = ea.UIN
        RIGHT JOIN 
            Filter_CTE ON ea.ZIP = Filter_CTE.Zip 
                       AND ea.STATE = Filter_CTE.State 
                       AND ea.CITY = Filter_CTE.City 
                       AND (ISNULL(ea.STREET1, '') + ' ' + ISNULL(ea.STREET2, '')) = Filter_CTE.StreetAddress
                       AND CONTAINS(en.SNAME,@SearchName)
            --END

    SET @counter += 1
END 

SELECT 
    UIN, NAME, STREETADDRESS, CITY, STATE, ZIPCODE, PHONE 
FROM 
    @result 

comment
вы можете попробовать совпадение like '%<yourname>%' вместо contains() - оно может быть быстрее в зависимости от оптимизации, выполненной sqlServer. оптимизация запросов сильно зависит от используемых данных и планов/индексов - если вам действительно нужно искать столько данных, это займет некоторое время.   -  person Patrick Artner    schedule 27.12.2017
comment
Вы уверены, что таблицы Names и Addresses имеют покрывающие индексы для всех столбцов, используемых в вашем предложении RIGHT JOIN Filter_CTE?   -  person andrews    schedule 28.12.2017
comment
Да, таблицы Names и Addresses имеют покрывающие индексы для всех столбцов (за исключением столбцов STREET1 и STREET2 из Addresses), используемых в вашем предложении RIGHT JOIN Filter_CTE.   -  person K.Engineer    schedule 28.12.2017
comment
что, если вы удалите условие AND CONTAINS(en.SNAME,@SearchName) из условия RIGHT JOIN? Ты пробовал? Будет ли это быстрее?   -  person andrews    schedule 28.12.2017


Ответы (1)


В настоящее время невозможно использовать имена столбцов в качестве условия поиска в СОДЕРЖИТ или СОДЕРЖИМОЕ. Таким образом, вы не можете выполнять прямое JOIN между таблицей данных и таблицей SearchFilter с применением предикатов FTS.

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

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

  1. пункт DISTINCT в INSERT INTO @result ... SELECT DISTINCT .... Это уровень, на котором вы JOIN обращаетесь к таблицам с миллионами записей. Хотя я понимаю, что конечный результат может содержать всего несколько тысяч строк, лучше перенести DISTINCT в эту строку:

    SELECT DISTINCT
        UIN, NAME, STREETADDRESS, CITY, STATE, ZIPCODE, PHONE 
    FROM 
        @result 
    
  2. Это условие AND (ISNULL(ea.STREET1, '') + ' ' + ISNULL(ea.STREET2, '')) = Filter_CTE.StreetAddress, безусловно, НЕ подлежит SARG. Вы используете конкатенацию и функцию (ISNULL()), которая не позволяет SQL Server использовать существующие индексы по таблице dbo.ADDRESSES ea. Проверьте этот вопрос: Что делает оператор SQL доступным для sargable?, чтобы узнать, как построить JOIN / WHERE условий таким образом, чтобы можно было использовать индексы. В этом конкретном случае лучше добавить вычисляемый столбец в таблицу dbo.Addresses, а затем построить по нему индекс (или добавить его в существующий индекс):

    CREATE TABLE [dbo].[ADDRESSES]
    (
        ...
        STREET as (ISNULL(ea.STREET1, '') + ' ' + ISNULL(ea.STREET2, '')),
        ...
    )
    

Поэтому исправьте приведенные выше 1. и 2. затем прокомментируйте условие AND CONTAINS(en.SNAME,@SearchName) в RIGHT JOIN и обратите внимание на время выполнения. После этого раскомментируйте условие CONTAINS и посмотрите, какая задержка была добавлена. Таким образом, вы будете точно знать, виноват ли в задержке FTS-движок или ваш основной запрос нуждается в доработке.

Чтобы иметь возможность дать больше рекомендаций, нам необходимо увидеть планы выполнения вашей процедуры. Вы можете поделиться своим планом выполнения запроса на этой странице: https://www.brentozar.com/pastetheplan/ .

ХТН

person andrews    schedule 28.12.2017
comment
Спасибо за ваше драгоценное время и усилия, которые вы потратили на мой вопрос. Я обновил логику в цикле while, а также реализовал одно из ваших предложений по вычислению столбца с индексом, что улучшило общую производительность хранимой процедуры. - person K.Engineer; 29.12.2017