Windows上用VS2008编译zhparser:PostgreSQL中文全文检索扩展指南

发布时间:2026/9/25 14:15:59
Windows上用VS2008编译zhparser:PostgreSQL中文全文检索扩展指南 简介面向Windows下PostgreSQL中文分词需求的开发者这份资源包提供了zhparser扩展在VS2008环境下的完整编译安装方案解决原版仅支持Linux、Windows下无现成工程的关键痛点。zhparser基于scws分词库设计作者通过自建VS2008项目整合scws源码与PostgreSQL头文件成功生成可直接加载的dll扩展让Windows服务器上的PostgreSQL也能实现中文全文检索。压缩包共138个文件大小约11.19MB内容以C/C源码.c/.h、VS工程文件.sln/.vcproj、编译产物.dll/.lib/.obj以及配置文件.ini/.control/.sql为主同时保留Makefile、configure等跨平台脚本方便对照理解编译机制。已有1600余人学习参考。除了编译产物资源内还总结了作者实测排错经验包括头文件编码转UTF-8、规避路径空格、修正导出函数声明等关键步骤并给出了分词词典与ini文件的正确放置位置和测试SQL语句可帮助读者快速复现配置过程避免重复踩坑。1. 在Windows上用VS2008编译zhparser一场绕不开的折腾如果要在Windows上的PostgreSQL里做中文全文搜索绕不开的坎就是默认parser不认中文而zhparser几乎是绕不开的方案。可惜这条路上一头是VS2008编译环境另一头是扩展模块必须和PostgreSQL二进制严格对齐中间还夹着一套要被一起编译的SCWS C代码。网上教程多在Linux下一条make install就收工Windows下远没这么便宜。这篇笔记把全流程讲透zhparser和SCWS各自的作用VS2008与PostgreSQL版本为什么绑死DLL怎么编出来并让PostgreSQL正常加载再把我自己踩过的坑和调参习惯一并写上。适合的目标读者是手上有旧Windows VS2008开发环境要给PostgreSQL通常是9.x加中文分词或者想彻底搞清楚扩展编译边界的人。我不打算假装这活简单但按这个顺序走能少折腾不少。2. 先摸清原理zhparser靠SCWS分词模块又和PostgreSQL绑死2.1 zhparser在PostgreSQL全文检索里的角色PostgreSQL的全文检索体系里有两个关键角色parser负责把文本切成原料token词典和映射再把token处理成可检索的lexeme。默认parser对英文按空格和标点切分切中文时基本等于把整段话揉成一个词检索也就无从谈起。zhparser的出现就是为了补这个缺——它作为PostgreSQL的text search parser扩展把中文按词交出来而不是按字。具体到实现层面zhparser向外暴露一组回调函数对应PostgreSQL parser必须实现的start、gettoken、end等接口。在SQL里执行CREATE TEXT SEARCH CONFIGURATION chinese (PARSER zhparser)PostgreSQL就把zhparser注册为parser后续全文检索查询都会走它切分中文。zhparser本身并不分词它把分词工作委托给SCWS库自己只做两件事把SCWS吐出来的词转成PostgreSQL能识别的token类型再把词频、位置等信息填回tsvector。2.2 SCWS真正分词的C库SCWS是一套C语言写的中文分词库原理上并不是简单的正向最大匹配而是基于词典和规则集的分词算法。它会先按词典做初步切分再用规则集处理未登录词、人名地名等特殊情况最终返回一组带词性和位置的词组。zhparser编译时把SCWS的源文件一起编进去运行时由SCWS的API加载词典文件dict.utf8.xdb和规则文件rules.utf8.ini。所以整个依赖链是PostgreSQL → zhparser → SCWS → 词典文件。任何一环没对上最终要么库加载失败要么分词结果只剩单字或乱码。Windows下编zhparser本质就是把zhparser.c和SCWS的全部.c文件一起编成DLL再让PostgreSQL进程加载这个DLL。比起Linux下由PGXS自动完成的流程Windows上这一套几乎全是手动活。2.3 ABI版本VS2008编译的DLL为什么不能随便配新版PostgreSQL加载扩展模块时会校验模块里导出的PG_MODULE_MAGIC宏这个宏携带PostgreSQL主版本号和编译期信息。版本对不上PostgreSQL直接拒绝加载报错常见是incompatible library。Windows下还有第二层约束PostgreSQL官方Windows二进制每个大版本对应的Visual Studio版本基本固定9.0到9.2时代的安装包就是VS2008编的之后的版本换成了更新的工具链。换句话说标题里的VS2008环境基本意味着面对的PostgreSQL是9.x早期版本。为什么说这是Windows特有的坑Linux下模块编译时只要找到同大版本的pg_configABI基本能对齐Windows下头文件和lib来自PostgreSQL安装目录但编译器版本如果和官方构建时不一致DLL在加载阶段就翻车。所以做这个安装程序的第一步其实是确认PostgreSQL版本和编译器配对的合理性而不是急着去编代码。注意VS2008对应MSVC 9.0。如果目标是PostgreSQL 9.3之后的版本先确认该版Windows官方二进制用的编译器用VS2008硬编出来的DLL很可能连加载都过不去。3. 准备构建环境源码、VS2008工程和PostgreSQL开发头文件3.1 需要准备的四样东西动手之前把材料备齐。zhparser源码一般从GitHub公开仓库拿选和PostgreSQL大版本兼容的分支。SCWS源码包要单独准备版本选zhparser适配的1.2.x系列别随手拿最新版。PostgreSQL安装目录必须有完整的include和lib子目录装的是开发版而不是精简运行版。最后是VS2008注意启动“Visual Studio 2008命令提示”不是普通CMD因为需要里面预设好的编译环境变量。版本不用死记但记住一条原则三方同步锁定。zhparser分支、SCWS版本、VS编译器版本全部围绕当前PostgreSQL大版本对齐。任何一个错位后面都会在“为什么加载不了”的排查上浪费大量时间。3.2 怎么组织源码目录目录结构直接影响命令行编译的顺利程度。常见的布局是这样zhparser-win/ ├── src/ │ ├── zhparser.c │ ├── zhparser.h │ └── scws/ │ ├── scws.c │ ├── scws.h │ ├── xdb.c │ ├── cht.c │ ├── ruleset.c │ └── ... ├── dict/ │ ├── dict.utf8.xdb │ └── rules.utf8.ini └── pg/ └── include/把SCWS源码解压后直接放到src/scws子目录避免后面头文件路径写得到处都是。zhparser源码的zhparser.c和zhparser.h放在src根目录。dict目录单独放词典文件它们不参与编译但后面部署时要找得到。PostgreSQL的include目录不用复制记住绝对路径即可。3.3 建立VS2008 DLL工程打开VS2008新建“Win32项目”向导里选DLL和空项目项目名就叫zhparser。然后把zhparser.c和scws目录下所有.c文件加进工程——这一步省略的话链接阶段会报SCWS内部函数符号找不到。接下来是项目属性设置。右键项目进属性在“C/C → 常规”的“附加包含目录”里填三处PostgreSQL的include目录、src目录、src/scws目录。“C/C → 代码生成”里“运行时库”选“多线程DLL(/MD)”要和PostgreSQL自身运行时一致。“C/C → 高级”里的“编译为”这里要动点脑筋。提示zhparser.c里有些写法对C99依赖VS2008的C编译器只支持C89直接编容易报语法错误。常见做法是把zhparser.c在文件属性里单独设成“编译为C”SCWS的.c文件保持C编译。同一个工程里混用C和C编译在VS2008下没问题项目属性能对单文件覆盖。链接阶段在“链接器 → 输入”的“附加依赖项”里加上PostgreSQL的lib目录下的postgres.lib。这一步解决zhparser.dll对PostgreSQL内部符号的引用没有它链接直接断在外部符号上。3.4 验证开发环境是否对齐不是装好PostgreSQL就有开发文件。Windows安装版一般把头文件和导入库都放安装目录但精简安装可能缺include。最快的验证办法是在VS2008命令提示下执行C:\Program Files\PostgreSQL\9.2\bin\pg_config --includedir能输出include路径说明开发文件在如果命令不存在要么没装pg_config要么bin目录不在PATH里。再用pg_config --version确认大版本号回头核对编译器版本匹配关系。还有一个容易忽略的目录PostgreSQL安装目录下的share\extension后面部署扩展控制文件会用到。先确认它存在不存在就手工创建。这一章的准备工作目标只有一个让VS2008编出的DLL在链接期拿到真实符号在运行期和PostgreSQL进程的内存布局对齐。准备环节省了后面翻车概率直线上升。4. 编译与部署从SCWS目标文件到zhparser.dll4.1 编译SCWS目标文件如果按3.3建了VS2008工程生成解决方案就能把整个工程编完。但走命令行更可控也方便看得见每一步的错误。SCWS在VS下编译用cl命令逐文件生成目标文件cl /nologo /c /O2 /MD /I .\src /I .\src\scws ^ /I C:\Program Files\PostgreSQL\9.2\include ^ .\src\scws\scws.c .\src\scws\xdb.c .\src\scws\cht.c .\src\scws\ruleset.c这条命令把SCWS的多个.c文件编译成.obj生成在当前目录。关键参数是/MD指定多线程DLL运行时/O2做速度优化。不要在这里加C编译选项保持C编译。如果报“无法打开包含文件”说明/I路径没给全回到3.3检查scws.h所在目录到底在哪。4.2 编译zhparser并链接生成DLL接着处理zhparser本体注意/TP参数cl /nologo /c /O2 /MD /TP /I .\src /I .\src\scws ^ /I C:\Program Files\PostgreSQL\9.2\include ^ .\src\zhparser.c /Fozhparser.obj/TP强制把zhparser.c按C编译器处理这是绕开VS2008 C编译器C99支持不足的关键。生成的zhparser.obj接下来和SCWS的obj一起参与链接link /nologo /DLL /OUT:zhparser.dll ^ zhparser.obj scws.obj xdb.obj cht.obj ruleset.obj ^ C:\Program Files\PostgreSQL\9.2\lib\postgres.lib链接阶段最常见的错误是unresolved external symbol。报错符号如果以scws_开头说明SCWS目标文件没链接全如果带PG_或MemoryContext字样是postgres.lib路径不对或者编译时头文件版本和lib版本不一致。链接成功后会生成zhparser.dll这并不等于大功告成。4.3 部署DLL、词典和扩展控制文件DLL生成后第一个去处是PostgreSQL的lib目录copy /Y zhparser.dll C:\Program Files\PostgreSQL\9.2\lib\词典文件放到tsearch_data目录PostgreSQL默认从那里加载分词数据。Windows下这个目录经常不存在需要创建if not exist C:\Program Files\PostgreSQL\9.2\share\tsearch_data mkdir C:\Program Files\PostgreSQL\9.2\share\tsearch_data copy /Y dict\dict.utf8.xdb C:\Program Files\PostgreSQL\9.2\share\tsearch_data\ copy /Y dict\rules.utf8.ini C:\Program Files\PostgreSQL\9.2\share\tsearch_data\还有一步容易被忽略PostgreSQL从9.1开始扩展必须有control文件CREATE EXTENSION才能用。zhparser源码里一般自带zhparser.control模板没有就自己写一个comment zhparser default_version 1.0 module_pathname $libdir/zhparser relocatable true把control文件和对应版本的SQL脚本放进share\extension目录copy /Y zhparser.control C:\Program Files\PostgreSQL\9.2\share\extension\ copy /Y zhparser--1.0.sql C:\Program Files\PostgreSQL\9.2\share\extension\SQL脚本内容就是标准CREATE FUNCTION声明zhparser的parser函数用AS MODULE_PATHNAME指向DLL。这步不做后面psql执行CREATE EXTENSION zhparser会报扩展不存在。全部落位后重启PostgreSQL服务net stop postgresql-9.2 net start postgresql-9.2服务名按本机实际服务名走。重启不是玄学DLL是新放入的PostgreSQL后端进程只在启动时加载扩展库。4.4 在psql里完成扩展注册打开psql切到目标数据库执行CREATE EXTENSION zhparser;成功返回CREATE EXTENSION。这一步报错集中在两类一类是could not open extension control file说明control文件放错目录另一类是could not load library说明DLL路径不对或ABI版本不兼容需要回第2章核对编译器配对关系。注册成功后创建中文全文检索配置CREATE TEXT SEARCH CONFIGURATION chinese (PARSER zhparser); ALTER TEXT SEARCH CONFIGURATION chinese ADD MAPPING FOR n,v,a,i,e,l WITH simple;第一句指定zhparser作为parser第二句把名词(n)、动词(v)、形容词(a)等词性映射到simple词典也就是不额外做词干归一化。这是最简配置先跑通再谈调优。最后用这条语句验证分词链路SELECT to_tsvector(chinese, 中文分词扩展安装测试);能看到按词切分的结果说明从编译到注册整条链路真正通了。到这里标题里“安装程序”的编译安装部分结束。5. 避坑指南WindowsVS2008编译zhparser的几个常见翻车点5.1 加载时报incompatible library现象CREATE EXTENSION zhparser时报could not load library ... incompatible libraryDLL加载阶段直接被拒。原因PG_MODULE_MAGIC宏携带的版本信息对不上。常见有两种情况一是PostgreSQL大版本不同二是编译器版本和官方Windows二进制不一致。VS2008编出的模块放进PostgreSQL 9.3之后几乎必然报这个错。解决先确认PostgreSQL大版本对应的编译器再决定是否换VS版本或换PostgreSQL版本。如果坚持用VS2008要选9.0到9.2这一代的PostgreSQL官方安装包。编译前用pg_config --version核对别等报错才想起来查。5.2 分词结果全是单字现象扩展加载成功ts_parse能跑但切出来的结果全是一个个汉字或带上奇怪的乱码字符。原因SCWS词典没有正确加载。zhparser运行时会去tsearch_data目录找dict.utf8.xdb找不到词典时SCWS退化成按单字切分。或者词典文件存在但中文编码不对加载时解析失败。解决确认dict目录下的词典文件已经复制到PostgreSQL的share\tsearch_data文件名严格保持dict.utf8.xdb。同时检查文件编码是UTF-8Windows下用记事本另存过容易变成带BOM的UTF-8这会干扰SCWS的加载逻辑。另外zhparser.dict_in_memory参数如果开着首次使用时会整包载入词典内存不足时也可能出现切分退化。5.3 VS2008把.cpp当C编后SCWS报类型转换错现象把scws.c也设成“编译为C”后编译报cannot convert from void * to char *在C严格类型检查下SCWS的C代码过不去。原因SCWS按C写C编译器的类型检查比C严格void隐式转char合法转成C就不认了。解决回到项目属性把scws目录下的.c文件单独设成“编译为C”只对zhparser.c用C编译。VS2008的项目属性支持对单文件覆盖编译选项在文件上右键进属性改“编译为”即可。5.4 链接报unresolved external symbol现象链接阶段报一堆未解析的符号仔细看都是scws_前缀或者MemoryContext之类。原因分两类。scws_开头的符号是SCWS的.obj没参与链接可能漏加了xdb.obj或cht.objMemoryContext、palloc这类PostgreSQL内部符号是postgres.lib路径不对或头文件版本和lib版本错位。解决先按报错符号前缀分类。SCWS符号缺了就去4.2的cl命令里补对应源文件PostgreSQL符号缺了确认附加依赖项里postgres.lib的绝对路径并且编译时用的include目录和lib来自同一个PostgreSQL安装目录。5.5 重启服务后PostgreSQL起不来现象按4.3部署完后执行net start服务启动失败事件查看器里指向zhparser.dll。原因DLL依赖的运行时库和PostgreSQL不一致。如果zhparser编的是/MT静态运行时而PostgreSQL用的是/MD进程里会有两套CRT轻则资源泄漏重则启动崩溃。还有一种可能是zhparser依赖了当前PostgreSQL版本里已被移除的符号。解决回到项目属性把“运行时库”改成“多线程DLL(/MD)”重新编译部署。如果问题依旧在事件查看器里看具体崩溃模块确认是不是还加载了旧版zhparser.dll没有覆盖干净。提示每次重新编译部署后务必重启PostgreSQL服务。曾经遇到过替换了DLL但服务不重启结果一直加载旧模块的情况排查半天才发现是自己在原地打转。6. 调优让中文全文检索真正可用6.1 快速验证安装结果装好扩展后先用一条SQL确认分词器工作状态SELECT token, lexemes FROM ts_debug(chinese, PostgreSQL中文分词扩展的调优实践);这会输出每个token、词性和映射后的lexeme。如果lexemes列出现不该有的停用词或单字说明参数需要调。6.2 开启multi_*参数改善召回率zhparser有一组multi_参数控制组合词是否参与切分。常见配置ALTER DATABASE postgres SET zhparser.multi_short on; ALTER DATABASE postgres SET zhparser.multi_duality on; SELECT set_config(zhparser.multi_zmain, on, false);multi_short开启后会把“中华人民共和国”这类长词同时切出“中华”“人民”“共和”等短词提升召回率但增加索引体积。multi_duality处理重叠歧义词。我的习惯是普通业务查询开multi_short就够重度搜索场景再叠加multi_zmain磁盘没那么紧张就让multi_duality也开着。6.3 标点忽略与停止词分词器默认会把标点当token处理影响索引质量。推荐打开忽略标点ALTER DATABASE postgres SET zhparser.punctuation_ignore on;对“的、了、吗”这类高频虚词用PostgreSQL自带stop word机制处理。先建一个简易词典CREATE TEXT SEARCH DICTIONARY simple_stop ( TEMPLATE pg_catalog.simple, STOPWORDS chinese );再把chinese配置里的a、d、u等虚词词性映射到这个词典而不是直接映射simple。这样可以避免“的”“了”把tsvector撑满。6.4 排序与查询写法全文检索排序用ts_rank时zhparser切出的词越短越容易命中所以默认排序下短词可能排在长词前面。建议查询里对长度做个加权SELECT id, title, ts_rank_cd(tsv, query) AS rank FROM articles, to_tsquery(chinese, 中文 分词) query WHERE tsv query ORDER BY rank DESC, char_length(title) ASC;把ts_rank_cd和标题长度一起用能在分词粒度不一致时保住排序质量。索引方面确保tsvector列上有GIN索引不然数据量上来后上面这条SQL会慢到不可用。这半年维护这套Windows下的zhparser最深的感觉是编译一次只解决“装得上”后面每个参数都是拿真实查询喂出来的。先跑通最小链路再根据业务搜索词逐步调整multi_*组合比一次性把参数全开要稳得多。希望这篇笔记帮到正在折腾同样问题的你。本文还有配套的精品资源点击获取