SQL Server 管理全文索引


全文索引不同於常見的聚集索引或非聚集索引,這些索引的內部實現是平衡樹(B-Tree)結構,而全文索引在物理上是由一系列的內部表(Internal tables)構成的,這些內部表稱作全文索引片段(Fragment),每一個索引片段也叫做一個倒轉索引(Inverted index),也就是說,每一個倒轉索引都是由一個內部表(Internal Table)實現的。

有一些好奇的朋友可能會發問:“為什么一個全文索引是由一系列的倒轉索引構成的,而不是一個?為什么叫做倒轉索引,把什么倒轉了?”

當新建全文索引之后,全文索引只有一個索引片段(Fragment),索引片段中沒有冗余的數據,從全文索引中執行contains命令時,只需要從這一個倒轉索引中查找,返回結果即可。隨着業務數據的更新,基礎表(underlying table)的數據也會更新,這時,全文索引會創建新的倒轉索引,但是,舊的數據沒有被刪除,這樣會導致倒轉索引的數量增加,也就是說,全文索引的片段增加,此時,全文索引就是由一系列的索引片段構成的。當全文索引的片段持續增多時,從全文索引中進行文本搜索需要查找更多的數據行,導致全文搜索的性能下降。在管理全文索引時,必須對索引片段進行重組(reorganize)或重建(rebuild),把已刪除的數據從索引片段中物理刪除,減少全文索引的片段數量,提高全文搜索的性能。

至於為什么稱作倒轉索引,這跟倒轉索引存儲的數據有關。

一,倒轉索引的結構

為了便於描述,把全文索引中存儲的一行數據叫做一個文檔,每一個文檔都使用唯一的文檔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
View Code

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
View Code

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
View Code

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;
View Code

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
View Code

字段 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時,表示索引片段已經是全文索引的一部分,可以被查詢,字段Timestamp表示該索引片段創建的時間戳,時間較晚的索引碎片中存儲的是最新的數據,而時間較早的索引片段中存儲的是被刪除/淘汰的數據。在執行全文查詢時,返回的結果中會丟棄被淘汰的數據。

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
View Code

 

 

 

參考文檔:

Manage and Monitor Semantic Search

Create and Manage Full-Text Indexes

Manage Full-Text Indexes

Improve the Performance of Full-Text Indexes

sys.dm_fts_parser (Transact-SQL)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM