Объединение полнотекстового и скалярного индекса

8

Допустим, у нас есть база данных из 12 миллионов имен и адресов, которые необходимо искать с помощью полнотекстового поиска, но каждая строка также содержит целочисленное значение, скажем COMPANYID. Таблица содержит около 250 различных идентификаторов COMPANYID по этим 12 миллионам строк.

Можно ли при определении полнотекстовых индексов дать каждой COMPANYсвоей «ветке» в дереве?

Тим
источник
Вы видите проблемы с производительностью сейчас? Например, если есть индекс CompanyID, и вы фильтруете по нему, видите ли вы такую ​​же производительность в полнотекстовом режиме, как если бы в этом столбце не было фильтра? У меня нет большого опыта работы с ним, и я надеюсь, что SQL Server достаточно умен, чтобы сузить пространство полнотекстового поиска до строк, которые в первую очередь соответствуют менее дорогому фильтру.
Аарон Бертран
На самом деле, я только что написал приложение для одного company, и всем так понравилось, что они хотят, чтобы я запустил его в производство для всех компаний, и у меня не было возможности создать макет с 12 миллионами значимых фиктивных строк данных. еще. Такие значения, как «Фамилия1», «Фамилия2», «Город1» и т. Д. Не будут иметь достаточного отклонения и могут исказить результаты теста. Данные меняются так часто, что я не уверен, что SQL Server будет достоверно знать, какой индекс является более узким в любом конкретном запросе, а количество строк в каждой компании сильно различается. Одна компания может иметь только 1000 строк, другая - 60 000.
Тим
Никто здесь не сможет предположить, учитывая уровень детализации, насколько хорошо SQL Server справится с этим сценарием. Вам нужно будет построить реалистичные, значимые тестовые данные и выполнить тесты вашей рабочей нагрузки на вашем оборудовании ...
Аарон Бертран
Но мне все равно хотелось бы получить ответ на мой вопрос. Я не прошу никого спекулировать.
Тим

Ответы:

3

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

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

Если у вас был какой-то серьезный объем (например, более 23 миллиардов записей), то вы могли бы взглянуть на решение с сегрегацией, например что-то вроде виртуальной машины Azure для каждой компании с приложением, расположенным перед ними, чтобы определить, к какой машине подключаться. Но, очевидно, вам это тоже не нужно.

В SQL 2008 также был ряд улучшений для полнотекстового, который теперь более интегрирован в ядро ​​базы данных. Один сценарий, в котором вы задаете предложение WHERE для обычного столбца и используете полнотекстовые функции, называется «смешанным запросом» и обсуждается здесь . Это все еще отличная статья, хотя информация для SQL 2008.

Если вы, как правило, беспокоитесь о производительности и планах, почему бы не ускорить некоторые тестовые данные, представьте некоторые перекосы и попробуйте их. Я выбил этот скрипт с ~ 2 миллионами строк за несколько минут:

!!TODO introduce some skew
USE master
GO

SET NOCOUNT ON
GO

DBCC TRACEON(610)   -- Minimal logging
GO

GO

IF EXISTS ( SELECT * FROM sys.databases WHERE name = 'fullTextDemo' )
BEGIN
    ALTER DATABASE fullTextDemo SET SINGLE_USER WITH ROLLBACK IMMEDIATE
    DROP DATABASE fullTextDemo
END
GO

IF NOT EXISTS ( SELECT * FROM sys.databases WHERE name = 'fullTextDemo' )
CREATE DATABASE fullTextDemo
GO

ALTER DATABASE fullTextDemo SET RECOVERY SIMPLE
GO

USE fullTextDemo
GO

IF OBJECT_ID('dbo.yourAddresses') IS NOT NULL DROP TABLE dbo.yourAddresses
IF OBJECT_ID('dbo.companies') IS NOT NULL DROP TABLE dbo.companies
GO

CREATE TABLE dbo.companies (
    companyId       INT IDENTITY NOT NULL,
    companyName     NVARCHAR(50) NOT NULL,

    CONSTRAINT PK_companies PRIMARY KEY ( companyId )
)
GO

CREATE TABLE dbo.yourAddresses (
    rowId           INT IDENTITY,
    companyId       INT NOT NULL FOREIGN KEY REFERENCES dbo.companies ( companyId ),
    searchTerms     NVARCHAR(2048) NOT NULL

    CONSTRAINT PK_yourAddresses PRIMARY KEY ( rowId )
)
GO

-- Populate the companies
;WITH cte AS (
SELECT TOP 250 ROW_NUMBER() OVER ( ORDER BY ( SELECT 1 ) ) rn
FROM master.sys.columns c1
    CROSS JOIN master.sys.columns c2
    CROSS JOIN master.sys.columns c3
)
INSERT INTO dbo.companies ( companyName )
SELECT NEWID()
FROM cte
GO

-- Generate 2,636,000 records
INSERT dbo.yourAddresses ( companyId, searchTerms )
SELECT c.companyId, m.[text]
FROM dbo.companies c
    CROSS JOIN ( SELECT * FROM sys.messages ) m
WHERE m.language_id = 1033
AND m.[text] Like '[a-z]%'
GO

CREATE INDEX _idx ON dbo.yourAddresses ( companyId ) INCLUDE ( searchTerms )
GO

-- !!TODO look at compression
--ALTER INDEX PK_yourAddresses ON dbo.yourAddresses REBUILD WITH ( DATA_COMPRESSION = PAGE )
--GO

-- Create the catalog
IF NOT EXISTS ( SELECT * FROM sys.fulltext_catalogs WHERE name = N'ftc_yourAddresses' )
CREATE FULLTEXT CATALOG ftc_yourAddresses
GO

-- Create the full-text index
CREATE FULLTEXT INDEX ON dbo.yourAddresses ( searchTerms ) KEY INDEX PK_yourAddresses ON ftc_yourAddresses WITH CHANGE_TRACKING MANUAL  -- CHANGE_TRACKING OFF, NO POPULATION
GO

SELECT 'before' ft, * FROM sys.fulltext_indexes
GO

ALTER FULLTEXT INDEX ON dbo.yourAddresses START FULL POPULATION;
GO


DECLARE @i INT 
SET @i = 0

WHILE EXISTS ( SELECT * FROM sys.fulltext_indexes WHERE has_crawl_completed = 0 )
BEGIN

        SELECT outstanding_batch_count, *
        FROM sys.dm_fts_index_population
        WHERE database_id = DB_ID()

        --SELECT *
        --FROM sys.dm_fts_outstanding_batches
        --WHERE database_id = DB_ID()

    WAITFOR DELAY '00:00:05'

    SET @i = @i + 1
    IF @i > 60 BEGIN RAISERROR( 'Too many loops!', 16, 1 ) BREAK END

END

SELECT 'after' ft, * FROM sys.fulltext_indexes
GO



SELECT TOP 1000 *
FROM dbo.yourAddresses ft
WHERE companyId = 42
 AND CONTAINS ( searchTerms, 'data' )
GO

SELECT TOP 1000 *
FROM dbo.yourAddresses a
    INNER JOIN CONTAINSTABLE ( dbo.yourAddresses, searchTerms, 'data' ) ct ON a.rowId = ct.[key]
WHERE a.companyId = 42
GO

SELECT TOP 1000 *
FROM dbo.yourAddresses a
    INNER JOIN CONTAINSTABLE ( dbo.yourAddresses, searchTerms, 'data' ) ct ON a.rowId = ct.[key]
WHERE a.companyId = 42
OPTION ( MERGE JOIN )
GO

SELECT TOP 100 *
FROM sys.dm_fts_index_keywords (DB_ID(), OBJECT_ID('dbo.yourAddresses') )

SELECT TOP 100 *
FROM sys.dm_fts_index_keywords_by_document(DB_ID(), OBJECT_ID('dbo.yourAddresses') )
ORDER BY document_id
GO
wBob
источник
Большое спасибо за то, что нашли время написать этот сценарий, за ссылку на статью со «смешанным» запросом и за перспективу миллиарды против миллионов :-)
Тим
1
Согласно статье, решение основной проблемы было представлено в SQL Server 2008.
Тим
Рад, что это было полезно. Я, вероятно, должен сказать, что индекс покрытия и подсказка в моем скрипте - это просто эксперименты, а не рекомендации, это варианты, которые могут у вас возникнуть, если у вас есть проблемы с производительностью. Индекс потенциально немного широк и применяются обычные предупреждения с подсказками.
wBob