Combinando texto completo e índice escalar

8

Digamos que tenemos una base de datos de 12 millones de nombres y direcciones que deben buscarse usando texto completo, pero cada fila también contiene un valor entero, digamos COMPANYID. La tabla contiene alrededor de 250 COMPANYID distintos en esos 12 millones de filas.

¿Es posible, al definir los índices de texto completo, dar a cada uno COMPANYsu propia "rama" en el árbol?

Tim
fuente
¿Estás viendo problemas de rendimiento ahora? Por ejemplo, si hay un índice en CompanyID y lo filtra, ¿está viendo el mismo rendimiento de texto completo que si no hubiera un filtro en esa columna? No tengo mucha experiencia, espero que SQL Server sea lo suficientemente inteligente como para reducir el espacio de búsqueda de texto completo a las filas que coinciden primero con el filtro menos costoso.
Aaron Bertrand
En realidad, acabo de escribir la aplicación para una companyhasta ahora, y a todos les gustó tanto que quieren que la ponga en producción para todas las empresas, y no he tenido la oportunidad de crear una maqueta con 12 millones de filas de datos ficticios significativos todavía. Los valores como "Apellido1", "Apellido2", "Ciudad1", etc. no tendrán suficiente variación y podrían sesgar los resultados de la prueba. Los datos cambian con tanta frecuencia que no estoy seguro de que SQL Server sepa de manera confiable qué índice es el más estrecho en una consulta determinada, y el número de filas por compañía varía mucho. Una compañía podría tener solo 1000 filas, otras 60,000.
Tim
Nadie aquí podrá especular, dado el nivel de detalle aquí, qué tan bien SQL Server manejará este escenario. Tendrá que construir datos de prueba realistas y significativos y ejecutar pruebas de su carga de trabajo en su hardware ...
Aaron Bertrand
Pero todavía me gustaría una respuesta a mi pregunta. No le estoy pidiendo a nadie que especule.
Tim

Respuestas:

3

No es la respuesta corta, y realmente no necesitas esto. Los índices de texto completo son índices invertidos, por lo que almacenan las palabras divididas por el doc_id exclusivo que debe especificar al crear el índice de texto completo. Debe ser una "columna única, de clave única, no anulable", idealmente un número entero. Lo que es esencialmente una clave foránea no figura y no hay una manera fácil de particionarlas sobre esa base.

Usted podría suplantar algo como esto con una mesa por empresa y el índice de texto completo por mesa. Necesitaría algún tipo de lógica de código sentado al frente para determinar desde qué tabla insertar / buscar. Esto sería un dolor de cabeza considerable para manejar, casi seguro que no vale la pena.

Si tuviera un volumen considerable (por ejemplo, más de 23 mil millones de registros), podría buscar una solución de fragmentación, por ejemplo, algo así como una máquina virtual de Azure por empresa con una aplicación sentada frente a ellos para determinar a qué máquina conectarse. Pero claramente tampoco necesitas eso.

También hubo una serie de mejoras en SQL 2008 al texto completo que ahora está más integrado en el motor de la base de datos. Un escenario, donde especifica una cláusula WHERE contra una columna normal y utiliza las funciones de texto completo, se conoce como 'Consulta mixta' y se discute aquí . Este sigue siendo un gran artículo a pesar de que la información es para SQL 2008.

Si generalmente está preocupado por el rendimiento y los planes, ¿por qué no mezclar algunos datos de prueba, introducir algún sesgo y probarlo? Golpeé este script con ~ 2 millones de filas en unos 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
fuente
Muchas gracias por tomarse el tiempo para escribir ese guión, por el enlace al artículo de consulta "mixto" y por la perspectiva de miles de millones frente a millones :-)
Tim
1
Según el artículo, se introdujo una solución al problema subyacente en SQL Server 2008.
Tim
Me alegra que haya sido útil. Probablemente debería decir que el índice de cobertura y la sugerencia de consulta en mi script son solo experimentos, no recomendaciones, estas son opciones que podría tener si tiene problemas de rendimiento. El índice es potencialmente un poco ancho y se aplican las advertencias habituales con sugerencias.
wBob