Combinando texto completo e índice escalar

8

Digamos que tenhamos um banco de dados de 12 milhões de nomes e endereços que precisam ser pesquisáveis ​​usando texto completo, mas cada linha também contém um valor inteiro, digamos COMPANYID. A tabela contém cerca de 250 COMPANYIDs diferentes nesses 12 milhões de linhas.

É possível, ao definir os índices de texto completo, atribuir a cada um o COMPANYseu "ramo" na árvore?

Tim
fonte
Você está vendo problemas de desempenho agora? Por exemplo, se houver um índice no CompanyID, e você filtrar por isso, está vendo o mesmo desempenho de texto completo como se não houvesse filtro nessa coluna? Não tenho muita experiência com isso. Espero que o SQL Server seja inteligente o suficiente para restringir o espaço de pesquisa de texto completo às linhas que correspondem primeiro ao filtro mais barato.
Aaron Bertrand
Na verdade, acabei de escrever o aplicativo para um companyaté agora e todo mundo gostou tanto que quer que eu o coloque em produção para todas as empresas, e não tive a chance de criar uma maquete com 12 milhões de linhas de dados fictícios significativas ainda. Valores como "Sobrenome1", "Sobrenome2", "Cidade1" etc. não terão variação suficiente e podem distorcer os resultados do teste. Os dados são alterados com tanta frequência que não tenho certeza se o SQL Server saberá com segurança qual índice é o mais estreito em qualquer consulta e o número de linhas por empresa varia bastante. Uma empresa pode ter apenas 1000 linhas, outras 60.000.
Tim
Ninguém aqui poderá especular, dado o nível de detalhe aqui, quão bem o SQL Server lidará com esse cenário. Você terá que construir dados de teste realistas e significativos e executar testes de sua carga de trabalho em seu hardware ...
Aaron Bertrand
Mas ainda gostaria de uma resposta para minha pergunta. Não estou pedindo a ninguém para especular.
Tim

Respostas:

3

Não, é a resposta curta e você realmente não precisa disso. Os índices de texto completo são invertidos e, portanto, armazenam as palavras divididas pelo único doc_id que você precisa especificar ao criar o índice de texto completo. Essa deve ser uma "coluna única, de chave única e não anulável", idealmente um número inteiro. O que é essencialmente uma chave estrangeira não aparece e não há uma maneira fácil de particioná-las nessa base.

Você pode falsificar algo assim com uma tabela por empresa e um índice de texto completo por tabela. Você precisaria de algum tipo de lógica de código à frente para determinar em qual tabela inserir / buscar. Isso seria uma dor de cabeça considerável para gerenciar quase certamente não vale a pena.

Se você tivesse um volume sério (por exemplo, mais de 23 bilhões de registros), poderia procurar uma solução de fragmentação, por exemplo, algo como uma VM do Azure por empresa com um aplicativo sentado à sua frente para determinar a qual máquina se conectar. Mas claramente você também não precisa disso.

Também houve várias melhorias no SQL 2008 para texto completo, que agora está mais integrado ao mecanismo de banco de dados. Um cenário, em que você especifica uma cláusula WHERE em uma coluna normal e usa as funções de texto completo, é referido como 'Consulta Mista' e discutido aqui . Este ainda é um ótimo artigo, embora as informações sejam do SQL 2008.

Se você geralmente está preocupado com o desempenho e os planos, por que não exibir alguns dados de teste, apresentar alguns desvios e testá-los. Eu bati esse script com ~ 2 milhões de linhas em alguns minutos:

!!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
fonte
Muito obrigado por ter tomado a tempo para escrever esse roteiro, para o link para o artigo consulta "misto", e para os bilhões contra milhões perspectiva :-)
Tim
11
De acordo com o artigo, uma solução para o problema subjacente foi introduzida no SQL Server 2008.
Tim
Ainda bem que foi útil. Provavelmente, devo dizer que o índice de cobertura e a dica de consulta no meu script são apenas experimentos, não recomendações, essas são opções que você pode ter se tiver problemas de desempenho. O índice é potencialmente um pouco amplo e os avisos usuais com dicas se aplicam.
wBob 7/09/14