Back to "正在修正站点@ ' @ search @ MS K1 @ postgreSQL full}\ -} 文本搜索 * ( 和断开位置"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

ASP.NET PostgreSQL RRF Search

正在修正站点@ ' @ search @ MS K1 @ postgreSQL full}\ -} 文本搜索 * ( 和断开位置

Wednesday, 14 January 2026

在真正用户开始输入真实查询之前,它看起来微不足道。 @ - @ acronnyms@ MS K2 一半的- 记得技术术语@,_ punctuation}-#Heavy names like @"_ ASPQ.NET}(或搜索) 概念上 但文字错误@. 当这些案例的搜索失败时 @ , @ 用户d '}t think @ MPK3_ 前置案例 @"} @ @ I-# 他们认为网站被打破了 @MS K6 @

从我观察这个网站开始以来, 它就一直困扰着它。 开放搜索, @Q}在 PostgreSQL 的完整文字搜尋中使用它@'#%s flatal 矢量素材, 但從來都不對此感到高興。 已窃听 特别是现在,I'm在 清晰RAG 区域包 我觉得我应该把它修好 是否是另一个问题 @. @

此篇文章涉及从零开始建立搜索引擎@. @ it'}有关在真实生产系统中修补 PostgreSQL full @-}文本搜索的尖锐边缘... .\ I'* 会走过特定失败, 我打中了%,}为什么它们会发生?,\ 和使搜索变得符合用户已经期待的方式所需的实用修正 {-}包括缩略语处理@MS K9}技术术语解析_,\ 短语 search\ ,} 排除=,} 以及 Google-}样式操作器\ MPK14}

以先前工作为基础本篇文章延伸至 语义搜索实施 这篇文章修正了执行失败的边缘案例: <:> 缩略语, 它与{'<,}技术术语和特殊字符匹配;自然查询是PostgreSQZL=9s passer break_.}

语义搜索基础设施具有双重用途@: @ it power the site='}%s user_-facing search 提供该数据的检索层。 GPT律师 RAG 系统 @-a 写作助理, 用网站@'#现有内容作为知识库来起草新职位=%. GPT "+#–+部分 嵌入和矢量搜索@. @%

问题

1. 空结果显示随机文章

当一个搜索返回没有结果时, 没有反馈@, @ 系统显示随机的旧文章而不是有用的建议@ .} 这违反了最不出人意料的原则 {-} 用户期待相关结果或明确的 {"}no 匹配"} mostK4 message 与最近的文章相匹配作为建议}.}这让搜索看起来有效了 只是严重地{.

固定: 修改 BlogSearchService.HybridSearchWithPagingAsync() 以在搜索返回零结果后进行检测,然后返回到显示按日期排列的最近职位 NoMatchFound 显示为 BasePagingModel 所以 UI 可以显示一个合适的消息, 如 @ " @ no count found@ MS K1_ _ 您指的是其中之一吗? @ ?"}

// No match found - return recent posts as suggestions
if (noMatchFound)
{
    Log.Logger.Information("No search results for '{Query}', returning recent posts as suggestions", query);
    return await GetRecentPostsAsSuggestions(targetLanguage, startDate, endDate, page, pageSize);
}

2. 类似 "Dise"WerenMS K3t匹配的缩略语

PostgreSQL ' @%s 默认英文文本搜索配置技术索引缩略语@,}但正常化和断开使得短,案例=-}在实际操作中重要术语不可靠 *.}当您寻找“"DISE}",}它被小写到‘"dise"}然后英语字典可以丢弃它或指定低重量的{,},从而导致完整- text搜索以错过标题和内容中明显包含 &"DiseMSMK12}的文章}

固定: 添加了缩略语检测@(Terms @≤6字符,上面有字母}) 以及用 PostgreSQL#'s 表示对子串敏感 ILIKE@._% 此功能补充完整@-# text search, 而不是取代它 @MS K2} <请参看下面的性能考量: 为什么这可以接受):

// Detect if query looks like an acronym or short term
var isAcronymLike = query.Length <= 6 && query.Any(char.IsUpper);

// Add substring search for acronyms
searchQuery = searchQuery.Where(x =>
    EF.Functions.ILike(x.Title, $"%{acronym}%")
    || EF.Functions.ILike(x.PlainTextContent, $"%{acronym}%"));

3. 特殊字符破碎搜索的技术术语

搜索 @"+ASP.+NET}或@"\C#"}将会失败,因为 PostgreSQL='}文本搜索解析器将时段和散列符号作为分隔符 {,} 将 &"ASP}.NET"拆分为 @MS K10ASS_"}和MS K12Net}MS13}这意味着要查找准确的术语 *%'#t working.}

固定*:* 创建 SearchQueryParser 承认通用技术术语并代之以可搜索版本@:

private static readonly Dictionary<string, string> TechnicalTerms = new(StringComparer.OrdinalIgnoreCase)
{
    ["asp.net"] = "aspnet",
    ["c#"] = "csharp",
    [".net"] = "dotnet",
    ["f#"] = "fsharp",
    ["node.js"] = "nodejs",
    // ... more terms
};

4.問候如 @"ASP}.NET與Alpine" 已做過'T工作

PostgreSQL 将 @"和"作为停止单词处理, 从搜索中删除@.。 加上特殊字符问题,一个自然查询, 如 @MS K4ASP}.NET 和 Alpine"基本上会成为搜索 *"Alpine " 仅

固定: 實施了一個 Google {-} 樣式的查詢解析器, 可以智慧地處理停止語言, 並支援高级搜尋操作員.}

解决方案

Google - sttyle 搜索操作器

我实施了一个 SearchQueryParser 类中使用支持":"来切换查询的类别

  1. 引文语句: "exact match" - 搜索确切的短语
  2. 不包括的术语: -unwanted - 不包括包含此术语的结果
  3. 通配符: ASP* @-Q 匹配@"ZASP",QQ#"ASPNET}","+ASPCree",等
  4. 技术术语: 自动手持 @"ASP}.NET", "C#", @#".Net"等 @MS K8

解析器使用已编译的正gex 模式表示查询@ :

[GeneratedRegex(@"""([^""]+)""|(-)?(\S+)", RegexOptions.Compiled)]
private static partial Regex QueryTokenRegex();

public ParsedQuery Parse(string query)
{
    var matches = QueryTokenRegex().Matches(processedQuery);

    foreach (Match match in matches)
    {
        // Quoted phrase
        if (match.Groups[1].Success)
        {
            var phrase = match.Groups[1].Value.Trim();
            result.Phrases.Add(phrase);
            continue;
        }

        // Excluded term (starts with -)
        if (match.Groups[2].Success)
        {
            var term = match.Groups[3].Value.Trim();
            result.ExcludeTerms.Add(term.ToLowerInvariant());
            continue;
        }

        // Regular term or wildcard
        var token = match.Groups[3].Value.Trim();
        if (token.Contains('*'))
        {
            result.WildcardTerms.Add(token.Replace("*", ""));
        }
        else if (!StopWords.Contains(token))
        {
            result.IncludeTerms.Add(token.ToLowerInvariant());
        }
    }
}

PostgreSQL tsquery 大楼

分析器输出输出结构化数据, 后转换为 PostgreSQL'%s tscquery syntax} 我们使用的数据 to_tsquery 因为我们正在生成结构化的语法 *\ -\ {不是} websearch_to_tsquery 因为我们已经解析了操作员 我们自己 , 给我们更多的控制 如何将条件合并

public string BuildTsQuery(ParsedQuery parsed)
{
    var queryParts = new List<string>();

    // Add include terms with AND
    foreach (var term in parsed.IncludeTerms)
    {
        queryParts.Add(term);
    }

    // Add wildcard terms with :* suffix
    foreach (var term in parsed.WildcardTerms)
    {
        queryParts.Add($"{term}:*");
    }

    return queryParts.Count > 0 ? string.Join(" & ", queryParts) : string.Empty;
}

为何不只使用 websearch_to_tsquery? 因为它在技术术语“,”上仍然失败, 缩略语“ ,” 和域名“ -” 特定语法“ @-” 没有提供混合排名或回补的钩子@.” 。 通过自我剖析“MS K5” 我们能够在这些边缘案例到达 PostgreSQL.之前处理它们。

增强建设搜索查询

缩略 BuildSearchQuery 完全重写的方法使用解析查询结构@.}请注意 baseQuery 使用语言已过滤@,}日期范围 @ ,和可见度 @ I- we'在上方添加搜索@ -特定条件\ :

private IOrderedQueryable<BlogPostEntity> BuildSearchQuery(
    string query,
    string language,
    DateTime? startDate,
    DateTime? endDate,
    string order)
{
    var parsed = _queryParser.Parse(query);
    IQueryable<BlogPostEntity> searchQuery = baseQuery;

    // Handle phrases (exact substring matching)
    foreach (var phrase in parsed.Phrases)
    {
        searchQuery = searchQuery.Where(x =>
            EF.Functions.ILike(x.Title, $"%{phrase}%")
            || EF.Functions.ILike(x.PlainTextContent, $"%{phrase}%")
            || x.Categories.Any(c => EF.Functions.ILike(c.Name, $"%{phrase}%")));
    }

    // Build tsquery for include terms and wildcards
    var tsQuery = _queryParser.BuildTsQuery(parsed);

    // Apply full-text search if we have terms
    if (!string.IsNullOrWhiteSpace(tsQuery))
    {
        searchQuery = searchQuery.Where(x =>
            x.SearchVector.Matches(EF.Functions.ToTsQuery("english", tsQuery))
            || x.Categories.Any(c =>
                EF.Functions.ToTsVector("english", c.Name)
                    .Matches(EF.Functions.ToTsQuery("english", tsQuery))));
    }

    // Handle acronyms with case-insensitive substring search
    // This supplements full-text search (additive OR), not replaces it
    var acronymTerms = parsed.IncludeTerms
        .Concat(parsed.WildcardTerms)
        .Where(t => _queryParser.IsAcronymLike(t))
        .ToList();

    foreach (var acronym in acronymTerms)
    {
        searchQuery = searchQuery.Where(x =>
            EF.Functions.ILike(x.Title, $"%{acronym}%")
            || EF.Functions.ILike(x.PlainTextContent, $"%{acronym}%"));
    }

    // Handle excluded terms (must NOT contain these)
    foreach (var excludeTerm in parsed.ExcludeTerms)
    {
        searchQuery = searchQuery.Where(x =>
            !EF.Functions.ILike(x.Title, $"%{excludeTerm}%")
            && !EF.Functions.ILike(x.PlainTextContent, $"%{excludeTerm}%")
            && !x.Categories.Any(c => EF.Functions.ILike(c.Name, $"%{excludeTerm}%")));
    }

    return orderedQuery;
}

搜索示例

以下是一些改进搜索功能的示例 @:

基本搜索

DiSE

@✅ 现在将文章和标题或内容中的 "DISE"匹配@(case{-#inchnize)

技术术语

ASP.NET

@✅}尋找關於 ASP.NET {(} 自动轉換成{MS K3aspnet"}的文章, 完整@- text search_)

词组搜索

"semantic search"

“✅”在文章中查找准确的短语@ " @semantic search@"

不包括的术语

ASP.NET -Core

@✅ 找到 ASP.NET 文章,

通配符

ASP*

✅Q配对@"ZASP",Q @"ASPNET}","*ASPNetCore#",等等

复杂查询

"full text search" PostgreSQL -MySQL

“✅”查找精确短语为“"”的文章,全文搜索@"和提到PostgresSQL,}但排除提及 MySQl 的文章

示例前后

查询: ASP.NET and Alpine

之前 (Broken):

  • @"#ASP#.#NET@"}分裂成 "_ASP4}+}("_NET}"}
  • "和"删除为停止字词
  • 仅搜索 @" @Alpine@"}

之后 (Fixed):

  • @"#ASP#.#NET@"}转换成 "_aspnet}"
  • "和" 智能保存作为用户查询意图的一部分
  • "Alpine"正常搜索
  • 结果:: 找到有关ASP.NET和阿尔卑斯山的文章

RRF 排名整合

该搜索与在《公约》中执行的对等阵列合并 +(RRRF) 先前的语义搜索文章.RRF在这里用于 排行排名, 記不起來- 它將 BMQ}-

// Fuse using RRF with category/freshness boosts
var fusedDtos = _ranker.FuseResults(bm25Results, vectorResults, query);

RRF算法的使用 1/(k+rank) k=60 来合并多个源的结果@ , 然后对\ MS K2应用助推

  • 类别匹配: +2.0
  • 标题匹配: +1.0
  • 新鲜“: ”在“1”年的指数衰变

RRF 的完整实施及混合搜索如何运作 <,>见 语义搜索在动作中. 更深的潜入嵌入和矢量相似性=, 见 GPT "+#-+部分“.” 与“-”相同的基础设施授权用户“MS K1” 搜索和 AAG 检索 AI- 辅助书写@.}

技术建筑

依赖性注射

新组件注册为服务@:

services.AddSingleton<SearchQueryParser>();
services.AddSingleton<SearchRanker>();
services.AddScoped<BlogSearchService>();

SearchQueryParserSearchRanker 它们是单吨,因为它们 @'re Nationalitate@,}线性 @MS K2#seany_,}可以按请求重新使用=.} BlogSearchService 因为它访问数据库上下文@. @%

查询流

  1. 用户输入搜索查询 → 搜索控制器
  2. 查询分析 →搜尋QueryParker 分解成结构化组件
  3. 语义搜索@( @ if 已启用 @ ) →Qdrant矢量数据库,带有ONNX嵌入功能
  4. *PostgreSQL 全@ - @ text search % MS K1} 总是可用的\ ) →BM25-}基于主控关键字匹配
  5. RRF 聚联 → 结合两种结果组和类别=/+#再生助推
  6. 无结果 @→_ 返回最近的职位作为建议

同一语义搜索组件也用于 GPT律师 系统以检索 AI- 辅助撰写@.\ 过去相关博客文章的系统 部分#4 接收管道的详细信息@. @%

数据库

搜索依赖于预先计算 SearchVector 列内 BlogPosts 表格:

ALTER TABLE mostlylucid."BlogPosts"
ADD COLUMN "SearchVector" tsvector
GENERATED ALWAYS AS (
    to_tsvector('english',
        coalesce("Title", '') || ' ' ||
        coalesce("PlainTextContent", '')
    )
) STORED;

CREATE INDEX idx_blog_posts_search_vector
ON mostlylucid."BlogPosts"
USING GIN ("SearchVector");

GIN {(} 通用反向索引} )} 提供快速完整搜索, 通过大文本 Corpora.}

业绩考量

为什么ILIKE Acronys ?

期间 ILIKE (case-灵敏度如 @) 慢于全@-text search}, it{'}因为%:所以需要缩略语

  1. 完整-text 搜索对齐@ /stems terms @ MS K2 打破短大字符串
  2. 缩略语一般简称为“(≤6”字符@),}限制性能影响
  3. 只有在需要时才添加此条件 @ ( @ 检测到的缩略语@ MS K1}{

此方法不会扩大为任意的子串搜索范围, 覆盖数以百万计的行@ - @} but for 目标缩略语 found it'_s applicate

PostgreSQL 覆盖密度排行ts_rank_cd)

PostgreSQL 提供两个排位函数, 用于 full @ -_ text search@ MS K1}

  • ts_rank“:”基本用词频率“(”多少次显示“MS K2”
  • ts_rank_cd“:”覆盖密度排行榜“(” 是否接近条件一起出现?“MS K2”

我们用 ts_rank_cd 因为它能提供 BM25- 类似关联 考虑术语的近似性

// Order by cover density ranking - rewards term proximity
orderedQuery = searchQuery.OrderByDescending(x =>
    x.SearchVector.RankCoverDensity(EF.Functions.ToTsQuery("english", tsQuery)));

为什么 ts_rank_cd 属于上端@: @%

Metric ts_rank ts_rank_cd
算数 @ 定期频率@ 覆盖密度 #( 近似性#)
多@ - @ 字查询 计为单数 奖赏 并列
例如:: "docker 容器" +#"@docker @"}#50+1+Docker conventions #"docker annex*$"#加起来是高分数{ #
业绩表现 实绩 快速”“ 边际变慢”「, 仍能影响 GIN 指数 @
行为行为 @ _简单计数@ _Apbounds BM25}#

这是 速赢优化 @ - 更好的相关性排序, 没有应用@ MS K1\ 级别计算@ MPK2} 所有排名都发生在 PostgreSQL, 使用现有的 GIN 指数 SearchVector.

参考文献::

额外绩效优化

除了修复搜索功能@,}几个关键优化可以改善性能 @:

1. 语言缓存

可用的语言很少改变, 但在每个搜索请求中都被询问@ . @ 现在用双-}\ checked locking\ MS K2}缓存

private static readonly TimeSpan LanguageCacheDuration = TimeSpan.FromHours(1);
private static List<string>? _cachedLanguages;
private static DateTime _languageCacheExpiry = DateTime.MinValue;
private static readonly SemaphoreSlim _cacheLock = new(1, 1);

影响:消除了每个搜索请求的数据库查询@.

2. 棒极了 ILIKE 查询

使用的原代码 foreach 环形循环创建多个条款区@. 现在被分解成单表达式 @:

// BEFORE: Multiple WHERE clauses
foreach (var acronym in acronymTerms)
{
    searchQuery = searchQuery.Where(x =>
        EF.Functions.ILike(x.Title, $"%{acronym}%"));
}

// AFTER: Single batched WHERE
if (acronymTerms.Count > 0)
{
    searchQuery = searchQuery.Where(x =>
        acronymTerms.Any(acronym =>
            EF.Functions.ILike(x.Title, $"%{acronym}%")));
}

影响“: ” 清洁 SQL @,}“~5-10% ” 为多@- 加快时间查询的速率

3. 常见查询覆盖索引

为经常访问模式添加了 INCLUDE 列中的部分指数@:

CREATE INDEX idx_blog_posts_search_covering
ON mostlylucid."BlogPosts" ("LanguageId", "IsHidden", "ScheduledPublishDate")
INCLUDE ("Id", "Slug", "Title", "PublishedDate")
WHERE "IsHidden" = false;

影响*: 启用 索引@-}只扫描 - PostgreSQL doesn't MS K1} t need to access the table heap, 减少用于常见查询的I/O

4. 已删除

EF 核心 Include() 移除了只有条款中所使用的导航属性@:

// BEFORE: Loads full LanguageEntity into memory
.Include(x => x.LanguageEntity)
.Where(x => x.LanguageEntity.Name == "en")

// AFTER: EF translates navigation property without loading entity
.Where(x => x.LanguageEntity.Name == "en")

影响@: {~5-10% 内存减少}, 减去从数据库传输的数据 □.

参考文献::

查询优化战略

正在逐步使用条款 EF 核心@' @%s 表达式树组成:

IQueryable<BlogPostEntity> searchQuery = baseQuery;

// Each filter added conditionally - PostgreSQL optimizes the final query
if (parsed.Phrases.Count > 0) { searchQuery = searchQuery.Where(...); }
if (!string.IsNullOrWhiteSpace(tsQuery)) { searchQuery = searchQuery.Where(...); }
if (acronymTerms.Count > 0) { searchQuery = searchQuery.Where(...); }

这产生 a 优化的 SQL 查询 “. PostgreSQL'}查询规划器在看到“.”条款的完整时,可以有效地使用统计和索引。

执行情况总表

所有优化的累积影响

QDB 负载撞击 * *
@ _ 语言缓存 @ I } 微小的 minimal @ MPK2 * MS K3} 查詢@ /} 請求@
移除包含 @() _ @#-5-10% 减数据传输@MS K5
批量 ILIGE MS K1 NSK2 I 清洁 SQL
包含索引 @ _-20-30% # 指数#- 仅扫描@
{\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}相似的
缓存修补 @(route params@) MS K3 N/A @ 防止停滞数据 @MS K6

预期改善总额: *#30-50% 快速搜索 大量减少数据库负载@. @%

经验教训

  1. Donó't 假设全部@-}文本搜索处理一切诸如缩写和特殊字符等:边缘案例需要特别处理@.

  2. 结合多种办法: full- text search @(BM}25)@+ 语义搜索{(VictorsMS K6 +子字符串回溯提供比任何单一方法更好的覆盖*.

  3. Google塑造的用户期望值@:# 支持引用的短语@, 排除%,\ 和通配符使搜索感觉自然, 因为用户已经熟悉这些操作器 @MS K3}

  4. 提供有益的后退@ : @ * 当搜索失败时 ',# dono\ ' t 显示任何不显示 MS K3 > 显示最近的文章, 并声明找不到匹配的@.\

  5. {\fn黑体\fs22\bord1\shad0\3aHBE\4aH00\fscx67\fscy66\2cHFFFFFF\3cH808080}小黑客: 适当的查询解析器比一系列字符串操作更清洁、更便于维护@.

  6. 使用 PostgreSQL' 建立@ - 排名: 使用 ts_rank_cd 代替 ts_rank 对于 BMQ25- 类似关联性@.覆盖密度排序认为“时间接近” @-是一个速赢,通过零应用来提高结果质量-级别代码

  7. 优化前的配置文件@: @MSK0@Obvious@"瓶颈 @MS K3_FTS 解析#)}是真正的问题吗?

  8. 批次数据库业务: 多 foreach 创建独立的循环,让条款产生亚优度 SQL.}使用 Any()All() 组合成单表达式@. @%

未来增强能力

未来可能作出的改进,}按关注分类*:

检索回收器改进:

  • 模糊匹配Levenshtein距离 用于伤寒耐受度
  • 同义词扩展@: "blog possible post @MS K2}→ " 条款", @{"#tutiment"

剖析增强:

  • 分类过滤: category:ASP.NET 操作员
  • 日期范围: after:2025-01-01 操作员

精炼:

  • 着重突出成果的成果: 在结果中显示匹配的文本部分
  • 通过跟踪单击“-”@:_ 学习用户行为来提高排名

可观察性:

  • 搜索分析: 跟踪民众查询,

结论结论

建筑生产 - 高级搜索需要固定边缘案例 优化性能@. @% 此篇文章覆盖了以下两个功能: 修补 PostgreSQL full @ -} text sort search scription case {acronnyms,}技术术语 ,Google-}风格操作员MS K7}以及执行关键性能优化 *caching},}批量=,}涵盖索引>,} ts_rank_cd).

执行成果 *#30-50% 快速搜索 正在通过 @ :\ 减少数据库负载

  • 覆盖密度排序 * @(}%ts_rank_cd*) 更贴切
  • 语言缓存删除重复查询
  • 较清洁 SQL 的 HIT ILIKE 表达式
  • 覆盖索引赋能指数@- @只扫描
  • 删除的不必要的 EF 核心包括

关键洞察@:}没有单一的方法能处理所有案例 . PostgreSQL FTS 在关键词匹配 < , 语义搜索中能够处理概念查询\ MS K3 和有针对性的后退抓边端病例. 干净的解析层将它连接起来\ , 处理操作员和技术条件,然后他们到达搜索引擎\□.}

语义搜索基础设施具有双重用途:

  • 用户“-” facking search: 结合关键字和语义匹配以取得更好的效果
  • RAG 检索: 授权 GPT律师 写作助理

相关文章 @:

正式文件=:

所有可用代码 MPK0s GitHub 主目錄.

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.