Combinaison d'index texte intégral et scalaire

8

Disons que nous avons une base de données de 12 millions de noms et adresses qui doivent être consultables en texte intégral, mais chaque ligne contient également une valeur entière, disons COMPANYID. Le tableau contient environ 250 COMPANYID distincts sur ces 12 millions de lignes.

Est-il possible, lors de la définition des indices de texte intégral, de donner à chacun COMPANYsa propre "branche" dans l'arborescence?

Tim
la source
Voyez-vous des problèmes de performances maintenant? Par exemple, s'il existe un index sur CompanyID et que vous filtrez dessus, voyez-vous les mêmes performances de texte intégral que s'il n'y avait pas de filtre sur cette colonne? Je n'ai pas une tonne d'expérience avec j'espère que SQL Server est assez intelligent pour restreindre l'espace de recherche en texte intégral aux lignes qui correspondent au filtre le moins cher en premier.
Aaron Bertrand
En fait, je viens d'écrire l'application pour une companyjusqu'à présent, et tout le monde l'a tellement aimé qu'ils veulent que je la mette en production pour toutes les entreprises, et je n'ai pas eu la possibilité de créer une maquette avec 12 millions de lignes de données factices significatives encore. Les valeurs comme "Lastname1", "Lastname2", "City1", etc. n'auront pas assez de variation et pourraient fausser les résultats du test. Les données changent si fréquemment que je ne suis pas sûr que SQL Server saura de manière fiable quel index est le plus étroit dans une requête donnée, et le nombre de lignes par entreprise varie considérablement. Une entreprise peut n'avoir que 1 000 lignes, 60 000 autres.
Tim
Personne ici ne pourra spéculer, étant donné le niveau de détail ici, sur la façon dont SQL Server gérera ce scénario. Vous devrez construire des données de test réalistes et significatives et exécuter des tests de votre charge de travail sur votre matériel ...
Aaron Bertrand
Mais j'aimerais quand même avoir une réponse à ma question. Je ne demande à personne de spéculer.
Tim

Réponses:

3

Non, c'est la réponse courte, et vous n'en avez pas vraiment besoin. Les index de texte intégral sont des index inversés de sorte qu'ils stockent les mots fractionnés par le doc_id unique que vous devez spécifier lorsque vous créez l'index de texte intégral. Il doit s'agir d'une "colonne unique, à clé unique, non nullable", idéalement un entier. Ce qui est essentiellement une clé étrangère ne figure pas et il n'y a pas de moyen facile de les partitionner sur cette base.

Vous pouvez usurper quelque chose comme ça avec une table par entreprise et un index de texte intégral par table. Vous auriez besoin d'une sorte de logique de code placée devant pour déterminer la table à insérer / récupérer. Ce serait un casse-tête considérable à gérer, ce n'est certainement pas la peine.

Si vous aviez un volume important (par exemple, plus de 23 milliards d'enregistrements), vous pourriez envisager une solution de partage, par exemple quelque chose comme une machine virtuelle Azure par entreprise avec une application assise devant eux pour déterminer à quelle machine se connecter. Mais vous n'avez clairement pas besoin de cela non plus.

Il y a également eu un certain nombre d'améliorations dans SQL 2008 au texte intégral qui est maintenant plus intégré dans le moteur de base de données. Un scénario, dans lequel vous spécifiez une clause WHERE par rapport à une colonne normale et utilisez les fonctions de texte intégral, est appelé «requête mixte» et décrit ici . Il s'agit toujours d'un excellent article même si les informations concernent SQL 2008.

Si vous êtes généralement préoccupé par les performances et les plans, pourquoi ne pas faire tourner certaines données de test, introduire un biais et l'essayer. J'ai assimilé ce script avec environ 2 millions de lignes en quelques minutes:

!!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
la source
Merci beaucoup d'avoir pris le temps d'écrire ce script, pour le lien vers l'article de requête "mixte", et pour la perspective des milliards contre des millions :-)
Tim
1
Selon l'article, une solution au problème sous-jacent a été introduite dans SQL Server 2008.
Tim
Heureux que ce soit utile. Je devrais probablement dire que l'index de couverture et l'indice de requête dans mon script ne sont que des expériences et non des recommandations, ce sont des options que vous pourriez avoir si vous rencontrez des problèmes de performances. L'index est potentiellement un peu large et les avertissements habituels avec des conseils s'appliquent.
wBob