Combinazione di indice full-text e scalare


8

Supponiamo di avere un database di 12 milioni di nomi e indirizzi che devono essere ricercabili utilizzando il testo completo, ma ogni riga contiene anche un valore intero, diciamo COMPANYID. La tabella contiene circa 250 distinti COMPANYID su quei 12 milioni di righe.

È possibile, quando si definiscono gli indici full-text, dare a ciascuno COMPANYil proprio "ramo" nella struttura?


Stai riscontrando problemi di prestazioni ora? Ad esempio, se esiste un indice su CompanyID e si filtra su quello, si sta riscontrando le stesse prestazioni full-text come se non ci fosse alcun filtro su quella colonna? Non ho molta esperienza con spero che SQL Server sia abbastanza intelligente da restringere lo spazio di ricerca full-text alle righe che corrispondono prima al filtro meno costoso.
Aaron Bertrand

In realtà, ho appena scritto l'app per uno companyfinora, e a tutti è piaciuto così tanto che vogliono che lo metta in produzione per tutte le aziende, e non ho avuto la possibilità di creare un modello con 12 milioni di righe di dati fittizi significativi ancora. Valori come "Cognome1", "Cognome2", "Città1", ecc. Non avranno variazioni sufficienti e potrebbero distorcere i risultati del test. I dati cambiano così frequentemente che non sono sicuro che SQL Server saprà in modo affidabile quale indice è il più stretto in una determinata query e il numero di righe per azienda varia notevolmente. Una società potrebbe avere solo 1000 righe, altre 60.000.
Tim

Nessuno qui sarà in grado di speculare, dato il livello di dettaglio qui, quanto SQL Server gestirà questo scenario. Dovrai costruire dati di test realistici e significativi ed eseguire test del tuo carico di lavoro sul tuo hardware ...
Aaron Bertrand

Ma vorrei ancora una risposta alla mia domanda. Non sto chiedendo a nessuno di speculare.
Tim

Risposte:


3

No è la risposta breve e non ne hai davvero bisogno. Gli indici full-text sono indici invertiti, quindi memorizzano le parole divise in base all'unico doc_id che devi specificare quando crei l'indice full-text. Questa deve essere una "colonna unica, a chiave singola, non nullable" idealmente un numero intero. Ciò che è essenzialmente una chiave esterna non figura e non esiste un modo semplice per suddividerle su tale base.

Si potrebbe falsificare qualcosa del genere con una tabella per azienda e un indice full-text per tabella. Avresti bisogno di una sorta di logica del codice seduta davanti per determinare da quale tabella inserire / recuperare. Questo sarebbe un notevole mal di testa da gestire, quasi certamente non ne vale la pena.

Se avessi un volume serio (ad esempio più di 23 miliardi di record), potresti guardare una soluzione di sharding, ad esempio qualcosa come una macchina virtuale di Azure per azienda con un'app seduta davanti a loro per determinare a quale macchina connettersi. Ma chiaramente non ne hai nemmeno bisogno.

Sono stati inoltre apportati numerosi miglioramenti in SQL 2008 al full-text, che ora è più integrato nel motore di database. Uno scenario, in cui si specifica una clausola WHERE su una colonna normale e si utilizzano le funzioni full-text, viene definito "query mista" e discusso qui . Questo è ancora un ottimo articolo anche se le informazioni sono per SQL 2008.

Se sei generalmente preoccupato per prestazioni e piani, perché non raccogliere alcuni dati di test, introdurre un po 'di inclinazione e provarli. Ho rovesciato questo script con ~ 2 milioni di righe in pochi minuti:

!!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

Grazie mille per aver dedicato del tempo a scrivere quello script, per il link all'articolo di query "misto" e per la prospettiva di miliardi contro milioni :-)
Tim

1
Secondo l'articolo, una soluzione al problema di fondo è stata introdotta in SQL Server 2008.
Tim

Sono contento che sia stato utile. Dovrei probabilmente dire che l'indice di copertura e il suggerimento per le query nel mio script sono solo esperimenti, non raccomandazioni, queste sono opzioni che potresti avere in caso di problemi di prestazioni. L'indice è potenzialmente un po 'ampio e si applicano i soliti avvisi con suggerimenti.
wBob,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.