一,倒轉索引的結構
為了便於描述,把全文索引中存儲的一行數據叫做一個文檔,每一個文檔都使用唯一的文檔ID(DocID)標識,這個DocID也就是在創建全文索引之前,必須創建的唯一索引鍵。
大家知道,全文索引中存儲的不是整個文本,而是把文本分詞之后,存儲單個標記(Token)的信息,標記(Token)是分詞,及其位置等信息的統稱。在填充全文索引的時候,分詞器(word breader)將字符串拆分成多個單詞。如果單詞是一個停用詞(stopword),那么該分詞被過濾掉,不會存儲到倒轉索引中,但是停用詞的位置(position)會被考慮,一個分詞在全文索引中的位置(Position)是該分詞在源文本中的位置。簡而言之,倒轉索引中存儲的數據是分詞和DocID之間的映射。
由DocID來查詢分詞,是正向的;而由分詞來定位DocID,是倒轉的,這就是倒轉索引名稱的由來。
例如,一個基礎表Document有兩列,DocumentID和Title,在字段Title上創建全文索引:

全文引擎首先要對Title字段的文本進行分詞,倒轉索引中主要包含四個字段:
- Keyword字段:單個分詞,從Title字段中抽取的一個標記(Token)。
- ColId字段:列序號,用於標記全文索引的列。
- DocId字段:文檔ID,是8Byte的long類型,用於唯一標記當前的文檔。如果唯一索引鍵是整數類型,那么DocID就是唯一索引鍵;如果唯一索引鍵不是整數類型,那么DocID經過中間的映射表、唯一映射到唯一索引鍵。因此,整數類型的唯一索引鍵能夠優化全文查詢的性能。
- Occurrence字段:分詞的位置,或者叫做偏移量(Offset)。
- CreateTime字段:時間戳字段(timestamp ),用於記錄倒轉索引創建的時間。
例如,下圖是Document表的索引片段Fragment1:

對於DocumentID=1的文檔,分詞器把Title字段拆分成5個分詞,這5個分詞分別是:Crank、Arm、and、Tire、Maintenance,出現的位置分別是1,2,3,4,5,由於分詞and是一個停用詞,過濾器會把分詞and過濾掉,但是and分詞的位置會計算在后續的分詞上。因此,分詞Tire的位置(Occurrence)是4,而不是3。
二,全文索引的拆分
全文索引通常會拆分成多個索引片段,一些索引片段可能包含新的數據,而一些索引片段可能包含已經被刪除的數據。例如,如果有一個用戶把DocumentID=3的文檔的Title字段更新為Rear Reflector:

全文索引會新建一個索引片段Fragmeng2,如下圖所示,

因此,如果有用戶查詢"Rear Reflector" ,DocID3將會被返回。每一個Fragment都會記錄創建的時間,當相同的DocID出現在不同的索引片段中,創建時間晚的是最新的數據。索引片段的創建時間可以從系統視圖:sys.fulltext_index_fragments 中查看。
三,全文索引片段的重組
當數據持續更新時,索引片段的數量也會持續增加,而全文查詢必須首先搜索每一個索引片段,然后丟棄無用的老數據,這會導致全文查詢的性能下降,必須減少索引片段的數量。由於每一個全文索引都屬於一個全文目錄(fulltext catalog),SQL Server使用TSQL 命令 ALTER FULLTEXT CATALOG 和REORGANIZE 選項對目錄中的所有全文索引進行重組操作。
SQL Server使用master merge來重組全文索引,也就是說,把全文索引的各個索引片段歸並到一個打的片段中,然后把廢棄的文檔從全文索引中刪除,這樣重組之后,全文索引中包含的都是純凈的數據:

在填充全文索引時,為了提高全文索引的填充速度,全文引擎使用Range來管理。Range是並行處理的進程,需要大量消耗CPU資源。batch是基礎表(underlying table)的數據塊,每個Range 都會產生多個batch,分batch處理數據能夠提高全文索引填充(population)的速度。SQL Server 從基礎表中讀取數據產生batch,每個batch經過全文引擎的處理,會產生一個索引片段。在填充操作完成后,SQL Server 會進行一次Master Merge 操作,將索引片段歸並到Master Fragment。
通過 sys.dm_fts_population_ranges 查看當前正在被處理的Ranges,每個Range都會分batch來處理。SQL Server 處理Batches的過程,可以通過 sys.dm_fts_outstanding_batches 來監控,其 crawl_memory_address column 指定其Parent Range。
全文索引的重組,可以設置調度程序(Schedule),通過Population Schedule tab,創建schedule和Job,按照schedule對全文索引進行重組(reorganize):

四,配置全文索引的停用詞
為了阻止全文索引把停用詞填充到全文索引中,SQL Server允許用戶自定義停用詞列表,把常用詞(這些詞對查詢沒有任何幫助)添加到停用詞列表中。在填充全文索引時,全文引擎會把停用詞過濾掉,這意味着,全文查詢不會搜索停用詞,盡管全文索引會忽略停用詞,但是,停用詞的位置會被考慮進去,每個分詞的位置是該分詞在源文本中的偏移量。
通過 CREATE FULLTEXT STOPLIST (Transact-SQL) 創建停用詞列表(StopLists),通過ALTER FULLTEXT STOPLIST (Transact-SQL) 向停用詞列表中增加和刪除停用詞(Stopword),通過ALTER FULLTEXT INDEX (Transact-SQL) 更新全文索引引用的停用詞列表,實例代碼如下:
CREATE FULLTEXT STOPLIST stoplist_name FROM SYSTEM STOPLIST; ALTER FULLTEXT STOPLIST stoplist_name ADD 'stopword' LANGUAGE language_term; ALTER FULLTEXT INDEX ON table_name SET STOPLIST =stoplist_name [WITH NO POPULATION];
五,查看分詞
分詞是全文引擎的一項重要的功能,通過 sys.dm_fts_parser 可以分詞器對文本分詞之后的結果,這也可以用於查看contains 子句產生的分詞:
sys.dm_fts_parser('query_string', lcid, stoplist_id, accent_sensitivity)
1,查看語句的分詞
SELECT fp.keyword, fp.group_id, fp.phrase_id, fp.occurrence, fp.special_term, fp.display_term, case fp.expansion_type when 0 then N'Single word case' when 2 then N'Inflectional expansion' when 4 then N'Thesaurus expansion/replacement' end as expansion_type, fp.source_term FROM sys.dm_fts_parser (' "The Microsoft business analysis" or "MS revenue" or "multi-million" ', 1033, 0, 0) as fp
2,查看contains 謂詞如何解析 FORMSOF 子句
查看同源詞, 'query_string' 的格式是: 'FORMSOF( INFLECTIONAL, query_term )'
SELECT fp.keyword, fp.group_id, fp.phrase_id, fp.occurrence, fp.special_term, fp.display_term, case fp.expansion_type when 0 then N'Single word case' when 2 then N'Inflectional expansion' when 4 then N'Thesaurus expansion/replacement' end as expansion_type, fp.source_term FROM sys.dm_fts_parser ('FORMSOF(INFLECTIONAL,run ) ', 1033, 0, 0) as fp

查看同義詞, 'query_string' 的格式是: 'FORMSOF( THESAURUS, query_term )'
SELECT fp.keyword, fp.group_id, fp.phrase_id, fp.occurrence, fp.special_term, fp.display_term, case fp.expansion_type when 0 then N'Single word case' when 2 then N'Inflectional expansion' when 4 then N'Thesaurus expansion/replacement' end as expansion_type, fp.source_term FROM sys.dm_fts_parser ('FORMSOF(THESAURUS,run ) ', 1033, 0, 0) as fp
六,查看全文索引的元數據
1,查看數據庫中的全文索引
select object_name(i.object_id) as TableName, i.unique_index_id, i.fulltext_catalog_id, c.name as fulltext_catalog_name, i.is_enabled, i.change_tracking_state_desc, i.crawl_type_desc, i.has_crawl_completed, iif(i.has_crawl_completed=1,datediff(minute,i.crawl_start_date,i.crawl_end_date),0) as crawl_duration_minute, i.crawl_start_date, i.crawl_end_date, i.incremental_timestamp, i.stoplist_id, i.data_space_id, ds.name as data_space_Name, ds.type_desc as data_sapce_type from sys.fulltext_indexes i inner join sys.data_spaces ds on i.data_space_id=ds.data_space_id inner join sys.fulltext_catalogs c on i.fulltext_catalog_id=c.fulltext_catalog_id
2,查看全文索引的所有分詞
declare @db_id int declare @table_id int set @db_id=db_id() set @table_id=object_id(N'schema_name.table_name',N'U') select kw.keyword, kw.display_term, kw.column_id, kw.document_count from sys.dm_fts_index_keywords(@db_id,@table_id) as kw
3,查看單個文檔的分詞
declare @db_id int declare @table_id int set @db_id=db_id() set @table_id=object_id(N'meetup.Events',N'U') select kw.keyword, kw.display_term, kw.column_id, kw.document_id, kw.occurrence_count from sys.dm_fts_index_keywords_by_document(@db_id,@table_id) as kw
4,查看全文索引的內部表(Internal Tables)
SELECT SCHEMA_NAME(itab.schema_id) AS [schema] ,itab.name AS internal_table_name ,typ.name AS column_data_type ,col.name as column_name ,col.column_id ,OBJECT_NAME(itab.parent_object_id) as base_table_name FROM sys.internal_tables AS itab INNER JOIN sys.columns AS col ON itab.object_id = col.object_id INNER JOIN sys.types AS typ ON typ.user_type_id = col.user_type_id where itab.internal_type_desc=N'FULLTEXT_COMP_FRAGMENT' ORDER BY itab.name, col.column_id;
5,查看每一個倒轉索引的大小和包含的數據行數
select object_name(table_id) as base_table_name, object_name(fragment_object_id) as fragment_table_name, fragment_id as Ordinal, status, data_size, row_count, [timestamp] from sys.fulltext_index_fragments
字段 Status 表示索引片段的狀態:
- 0 = Newly created and not yet used
- 1 = Being used for insert during fulltext index population or merge
- 4 = Closed. Ready for query
- 6 = Being used for merge input and ready for query
- 8 = Marked for deletion. Will not be used for query and merge source.
當狀態值為4或6時,表示索引片段已經是全文索引的一部分,可以被查詢,字段
6,查看全文索引填充的狀態
通過sys.dm_fts_index_population 查看當前正在運行的填充,每一次填充都會分多個Ranges並行填充,每一個Range可以分多個Batch進行。
select ip.database_id, object_name(ip.table_id,ip.database_id) as table_name, c.name as catalog_name, ip.population_type,ip.population_type_description, ip.is_clustered_index_scan, ip.range_count, ip.completed_range_count, ip.outstanding_batch_count, ip.status, ip.status_description, pr.session_id as Range_SessionID, pr.processed_row_count, ob.batch_id from sys.dm_fts_index_population ip left join sys.fulltext_catalogs c on ip.catalog_id=c.fulltext_catalog_id left join sys.dm_fts_population_ranges pr on ip.memory_address=pr.parent_memory_address left join sys.dm_fts_outstanding_batches ob on pr.memory_address=ob.crawl_memory_address order by ob.batch_id
參考文檔:
Manage and Monitor Semantic Search
Create and Manage Full-Text Indexes
