diff --git a/SourceCode/Code2026/CoreCommand/CoreTools.cpp b/SourceCode/Code2026/CoreCommand/CoreTools.cpp index fdc5d1a..3fb2da2 100644 --- a/SourceCode/Code2026/CoreCommand/CoreTools.cpp +++ b/SourceCode/Code2026/CoreCommand/CoreTools.cpp @@ -4911,7 +4911,15 @@ int CoreTools::getCurrentTrade() CString CoreTools::getCurrentPipeSysTblName() { + //表名随行业变化,所以按 getCurrentTrade() 缓存; + //指针故意不释放,避免 DLL 卸载时的析构顺序问题 + static int *s_pCachedTrade = new int(-1); + static CString *s_pCachedTable = new CString; + int nTrade = getCurrentTrade(); + if (*s_pCachedTrade == nTrade && !s_pCachedTable->IsEmpty()) + return *s_pCachedTable; + CString sRel(_T("WConfig_CN")); CUIISQLiteServer db; if (!db.InitADOServer(FilePath::GetSysDir() + _T("\\Plumbing.db"))) return sRel; @@ -4924,36 +4932,106 @@ CString CoreTools::getCurrentPipeSysTblName() db.GetSelData(sql, sRel); + + *s_pCachedTrade = nTrade; + *s_pCachedTable = sRel; return sRel; } +namespace +{ + //一个管线系统的四个样式字段,全部按字符串缓存 + struct PipeSysStyle + { + CString sColor; //颜色 + CString sLt; //线型名 + CString sLw; //线宽 + CString sLy; //图层名 + }; + + typedef std::map PipeSysStyleMap; + + //指针故意不释放,避免 DLL 卸载时的析构顺序问题 + PipeSysStyleMap& PipeSysStyleCache() + { + static PipeSysStyleMap *s_pMap = new PipeSysStyleMap; + return *s_pMap; + } + + //一条 SQL 取全四个字段;数据库打不开返回 false + bool QueryPipeSysStyle(LPCTSTR pszSys, bool bCenter, PipeSysStyle& st) + { + CUIISQLiteServer db; + if (!db.InitADOServer(FilePath::GetSysDir() + _T("\\Plumbing.db"))) return false; + + CString sTable(CoreTools::getCurrentPipeSysTblName()); + CString pipesys = bCenter ? _T("管线中心线") : _T("管线外边线"); + + CString sql; + sql.Format(_T("SELECT [COLOR],[LINETYPE],[LINEWIDTH],[LAYNAME] FROM %s WHERE [SYSTEM]='%s' AND [PIPESYS]='%s'") + , sTable, pszSys, pipesys); + + CStringArray arVals; + db.GetSelData(sql, arVals); + + if (arVals.GetSize() < 4) + { //查不到:沿用原来的默认值,也照样缓存,避免配错的系统名反复查库 + st.sColor = _T("256"); + st.sLt = _T("Continuous"); + st.sLw = _T("0"); + st.sLy = _T("0"); + return true; + } + + st.sColor = arVals.GetAt(0); + st.sLt = arVals.GetAt(1); + st.sLw = arVals.GetAt(2); + st.sLy = arVals.GetAt(3); + return true; + } + + //进程级缓存,key = 表名 + 系统名 + 中心线/外边线; + //只缓存字符串,AcDbObjectId 属于当前图纸,由调用方每次现解析 + bool GetPipeSysStyle(LPCTSTR pszSys, bool bCenter, PipeSysStyle& st) + { + CString sKey; + sKey.Format(_T("%s\x01%s\x01%d") + , (LPCTSTR)CoreTools::getCurrentPipeSysTblName(), pszSys, bCenter ? 1 : 0); + + PipeSysStyleMap& mapCache = PipeSysStyleCache(); + PipeSysStyleMap::const_iterator it = mapCache.find(sKey); + if (it != mapCache.end()) + { + st = it->second; + return true; + } + + PipeSysStyle stNew; + if (!QueryPipeSysStyle(pszSys, bCenter, stNew)) return false; + + mapCache[sKey] = stNew; + st = stNew; + return true; + } +} + int CoreTools::getColorByPipeSys(LPCTSTR sys, bool bCenter) { - CUIISQLiteServer db; - if (!db.InitADOServer(FilePath::GetSysDir() + _T("\\Plumbing.db"))) return 256; + PipeSysStyle st; + if (!GetPipeSysStyle(sys, bCenter, st)) return 256; - CString sTable(CoreTools::getCurrentPipeSysTblName()); - - CString pipesys = bCenter ? _T("管线中心线") : _T("管线外边线"); - CString sql; - sql.Format(_T("SELECT [COLOR] FROM %s WHERE [SYSTEM]='%s' AND [PIPESYS]='%s'"), sTable, sys, pipesys); - CString sClr(_T("256")); - db.GetSelData(sql, sClr); - return _ttoi(sClr); + return _ttoi(st.sColor); } AcDbObjectId CoreTools::getLtByPipeSys(LPCTSTR sys, bool bCenter) { AcDbObjectId id(AcDbObjectId::kNull); - CUIISQLiteServer db; - if (!db.InitADOServer(FilePath::GetSysDir() + _T("\\Plumbing.db"))) return id; - CString sTable(CoreTools::getCurrentPipeSysTblName()); - CString pipesys = bCenter ? _T("管线中心线") : _T("管线外边线"); - CString sql; - sql.Format(_T("SELECT [LINETYPE] FROM %s WHERE [SYSTEM]='%s' AND [PIPESYS]='%s'"), sTable, sys, pipesys); - CString sLt(_T("Continuous")); - db.GetSelData(sql, sLt); + PipeSysStyle st; + if (!GetPipeSysStyle(sys, bCenter, st)) return id; + + CString sLt(st.sLt); + if (sLt.IsEmpty()) sLt = _T("Continuous"); AcDbDatabase *pDb = acdbCurDwg(); if (!pDb) return id; @@ -4977,15 +5055,11 @@ AcDbObjectId CoreTools::getLtByPipeSys(LPCTSTR sys, bool bCenter) double CoreTools::getLwByPipeSys(LPCTSTR sys, bool bCenter) { - CUIISQLiteServer db; - if (!db.InitADOServer(FilePath::GetSysDir() + _T("\\Plumbing.db"))) return 0; + PipeSysStyle st; + if (!GetPipeSysStyle(sys, bCenter, st)) return 0; - CString sTable(CoreTools::getCurrentPipeSysTblName()); - CString pipesys = bCenter ? _T("管线中心线") : _T("管线外边线"); - CString sql; - sql.Format(_T("SELECT [LINEWIDTH] FROM %s WHERE [SYSTEM]='%s' AND [PIPESYS]='%s'"), sTable, sys, pipesys); - CString sLw(_T("0")); - db.GetSelData(sql, sLw); + CString sLw(st.sLw); + if (sLw.IsEmpty()) sLw = _T("0"); AecDbObjXDataMap xdata; if (KTModelLayoutXData::ReadModelXData(KT_PIPEINIT, xdata)) @@ -5007,15 +5081,12 @@ double CoreTools::getLwByPipeSys(LPCTSTR sys, bool bCenter) AcDbObjectId CoreTools::getLyByPipeSys(LPCTSTR sys, bool bCenter, bool bUpdate) { AcDbObjectId id; - CUIISQLiteServer db; - if (!db.InitADOServer(FilePath::GetSysDir() + _T("\\Plumbing.db"))) return id; - CString sTable(CoreTools::getCurrentPipeSysTblName()); - CString pipesys = bCenter ? _T("管线中心线") : _T("管线外边线"); - CString sql; - sql.Format(_T("SELECT [LAYNAME] FROM %s WHERE [SYSTEM]='%s' AND [PIPESYS]='%s'"), sTable, sys, pipesys); - CString sLy(_T("0")); - db.GetSelData(sql, sLy); + PipeSysStyle st; + if (!GetPipeSysStyle(sys, bCenter, st)) return id; + + CString sLy(st.sLy); + if (sLy.IsEmpty()) sLy = _T("0"); AcDbDatabase *pDb = acdbCurDwg(); if (!pDb) return id; diff --git a/sqlite_query_cache.md b/sqlite_query_cache.md new file mode 100644 index 0000000..f951c26 --- /dev/null +++ b/sqlite_query_cache.md @@ -0,0 +1,284 @@ +# AecDbEwPipe / AecDbEwFitting 初始化时的 sqlite 访问分析与修复方案 + +> 结论:这两个类本身不直接调用 sqlite。初始化阶段(`dwgInFields`,即打开图纸加载实体时)的大量 sqlite 访问全部通过 `CoreTools` 的 4 个样式查询 getter 间接产生。 +> +> 状态(2026-10-27):第 7 节方案已落地到 `CoreCommand/CoreTools.cpp`,R190 Debug 下 `AecRxBase` / `CoreCommand_BAL_REMOTE` / `CoreLibrary_BAL_REMOTE` / `CoreImplement` 四个工程编译通过。详见第 12 节。 + +--- + +## 1. 直接结论 + +- `AecDbEwPipe.cpp` / `AecDbEwFitting.cpp` / `AecDbEwPipeBase.cpp` 中**没有任何** `CUIISQLiteServer` / `InitADOServer` / `GetSelData` 直接调用。 +- 这两个类的 sqlite 访问 100% 走 `CoreTools` 的 4 个 getter: + - `getColorByPipeSys` + - `getLtByPipeSys` + - `getLwByPipeSys` + - `getLyByPipeSys` +- 构造函数很轻(只 `SetAt` 几个 XData 键,不碰库);`dxfInFields` 直接返回 `eOk`;`GetPipeParam` / `GetFittingName` 等也只读内存里的 `m_mapSpecXData`。 + +## 2. 初始化热点在 `dwgInFields` + +`AecDbEwPipe` 和 `AecDbEwFitting` 在加载实体时,`dwgInFields` 里有一段补样式键的代码: + +- `AecDbEwPipe::dwgInFields` —— `CoreLibrary/AecDbEwPipe.cpp:990-1074` +- `AecDbEwFitting::dwgInFields` —— `CoreLibrary/AecDbEwFitting.cpp:609-680` + +两段代码几乎一模一样: + +```cpp +CString sSys; +if (m_mapSpecXData.GetAt(PIPE_SYS, sSys)) +{ + int nvt; + if (!m_mapSpecXData.HasKey(PIPE_COLOR, nvt)) { clr = CoreTools::getColorByPipeSys(sSys, true); ... } + if (!m_mapSpecXData.HasKey(PIPE_LINETYPE, nvt)) { idLt = CoreTools::getLtByPipeSys(sSys, true); ... } + if (!m_mapSpecXData.HasKey(PIPE_LINELAYER, nvt)) { idLy = CoreTools::getLyByPipeSys(sSys, true); ... } + if (!m_mapSpecXData.HasKey(PIPE_LINEWIDTH, nvt)) { dw = CoreTools::getLwByPipeSys(sSys, true); ... } + // 外边线再来一遍(getXxx(..., false)) + ... +} +``` + +即:**只要 XData 里 `PIPE_SYS` 存在、而某个样式键缺失,就逐键调用 4 个 getter,中心线 + 外边线一共最多 8 次。** + +## 3. 每个 getter 的开销(已在 CoreTools.cpp 确认) + +`getColorByPipeSys / getLtByPipeSys / getLwByPipeSys / getLyByPipeSys` +(`CoreCommand/CoreTools.cpp:4930 / 4945 / 4978 / 5007`)每个都: + +1. `new CUIISQLiteServer`(构造里 `CoInitialize`) +2. `InitADOServer(...\Plumbing.db)` → `sqlite3_open` +3. 调 `getCurrentPipeSysTblName()`(`CoreTools.cpp:4912`)——**又新建一个 server、又 open 一次库**,再 `SELECT [TABLENAME] FROM WPipeCfg` +4. 自己再跑一条 `SELECT [COLOR|LINETYPE|LINEWIDTH|LAYNAME] FROM ...` +5. 析构 → `sqlite3_close` + `CoUninitialize` + +⇒ **1 个 getter ≈ 2 次 `sqlite3_open` + 2 条 SQL + 2 次 COM 初始化/反初始化。** + +`dwgInFields` 里 8 个 getter = **每个实体最多 ~16 次 open + 16 条 SQL**。且 `getCurrentPipeSysTblName()` 被重复调用 8 次,每次都独立开一次库查 `WPipeCfg`。 + +## 4. 更糟的点:`getLyByPipeSys` 会递归再查库 + +`getLyByPipeSys`(`CoreTools.cpp:5007`)里,当图层不存在需要新建时,还会再调 `getLtByPipeSys` + `getColorByPipeSys`(各再 +2 open +2 SQL)。因此单次 `getLyByPipeSys` 最坏可达 **6 次 open + 6 条 SQL**。图纸里图层没建全时,`dwgInFields` 的开销会进一步放大。 + +## 5. 量级估算 + +打开一张有 N 根管 + M 个管件的图纸: + +- 样式键已写全(正常保存的图):`HasKey` 全部命中,**不查库**。 +- 样式键缺失(老版本图 / 外来图 / 首次由旧版数据升级):每个实体最多 ~16 次 open。几百个实体就是**几千次串行的 `sqlite3_open`**,这就是"初始化非常慢"的直接来源。 + +## 6. 调用点统计(不含 CoreTools.cpp 内部) + +| 函数 | 独立调用点 | 是否开库 | +| --- | --- | --- | +| `getCurrentPipeSysTblName` | 46 | **每次都开库** | +| `getColorByPipeSys` | 23 | 是 | +| `getLtByPipeSys` | 21 | 是 | +| `getLwByPipeSys` | 41 | 是 | +| `getLyByPipeSys` | 61 | 是(且递归再查) | +| `getLyForPipe` | 24 | 否(只操作图层表) | +| `getPipeColorBySystem` | 1 | 是 | + +合计约 192 处热点调用,全部收敛到 `CoreTools.cpp`,**改这一个文件即全覆盖**。 + +## 7. 方案:进程级缓存(推荐,只改 `CoreTools.cpp`) + +核心思路:把"4 条 SQL + 反复开库"压缩成"一次查询 + 内存缓存"。 + +### 7.1 第一步:缓存表名 + +`getCurrentPipeSysTblName()` 改成按行业缓存: + +```cpp +CString CoreTools::getCurrentPipeSysTblName() +{ + static int s_cachedTrade = -1; + static CString s_cachedTable; + + int nTrade = getCurrentTrade(); + if (s_cachedTrade == nTrade && !s_cachedTable.IsEmpty()) + return s_cachedTable; + + CString sRel(_T("WConfig_CN")); + CUIISQLiteServer db; + if (!db.InitADOServer(FilePath::GetSysDir() + _T("\\Plumbing.db"))) return sRel; + + CString sql; + if (nTrade == 6) + sql.Format(_T("SELECT [TABLENAME] FROM WPipeCfg WHERE [STDNAME]='冶金'")); + else + sql.Format(_T("SELECT [TABLENAME] FROM WPipeCfg WHERE [STDNAME]='默认'")); + db.GetSelData(sql, sRel); + + s_cachedTrade = nTrade; + s_cachedTable = sRel; + return sRel; +} +``` + +key 带 `getCurrentTrade()` 是因为表名随行业(默认/冶金)变化;换图后 trade 变了会重新查,正确。 + +### 7.2 第二步:4 字段一条 SQL + 进程级缓存 + +在 `CoreTools.cpp` 的匿名 namespace 里加: + +```cpp +struct PipeSysStyle +{ + CString sColor; // 颜色(字符串) + CString sLt; // 线型名 + CString sLw; // 线宽 + CString sLy; // 图层名 +}; + +// 一条 SQL 取全 4 个字段 +bool QueryPipeSysStyle(LPCTSTR pszSys, bool bCenter, PipeSysStyle& st) +{ + CUIISQLiteServer db; + if (!db.InitADOServer(FilePath::GetSysDir() + _T("\\Plumbing.db"))) return false; + + CString sTable = CoreTools::getCurrentPipeSysTblName(); + CString sPipeSys = bCenter ? _T("管线中心线") : _T("管线外边线"); + CString sql; + sql.Format(_T("SELECT [COLOR],[LINETYPE],[LINEWIDTH],[LAYNAME] FROM %s WHERE [SYSTEM]='%s' AND [PIPESYS]='%s'"), + (LPCTSTR)sTable, pszSys, (LPCTSTR)sPipeSys); + + CStringArray ar; + db.GetSelData(sql, ar); + if (ar.GetSize() < 4) + { + st.sColor = _T("256"); st.sLt = _T("Continuous"); + st.sLw = _T("0"); st.sLy = _T("0"); + return true; + } + st.sColor = ar.GetAt(0); st.sLt = ar.GetAt(1); + st.sLw = ar.GetAt(2); st.sLy = ar.GetAt(3); + return true; +} + +// 进程级缓存(key = 表名 + 系统名 + 中心线/外边线) +bool GetPipeSysStyle(LPCTSTR pszSys, bool bCenter, PipeSysStyle& st) +{ + static std::map s_map; + + CString key; + key.Format(_T("%s\x01%s\x01%d"), + (LPCTSTR)CoreTools::getCurrentPipeSysTblName(), pszSys, bCenter ? 1 : 0); + + std::map::iterator it = s_map.find(key); + if (it != s_map.end()) { st = it->second; return true; } + + PipeSysStyle stNew; + if (!QueryPipeSysStyle(pszSys, bCenter, stNew)) return false; + s_map[key] = stNew; + st = stNew; + return true; +} +``` + +### 7.3 第三步:4 个 getter 改造成读缓存 + +```cpp +int CoreTools::getColorByPipeSys(LPCTSTR sys, bool bCenter) +{ + PipeSysStyle st; + if (!GetPipeSysStyle(sys, bCenter, st)) return 256; + return _ttoi(st.sColor); +} +``` + +`getLtByPipeSys` / `getLwByPipeSys` / `getLyByPipeSys` 保留原有副作用(解析线型 id、乘图纸比例、建/找图层),只是**数据来源换成 `st`**: + +- `getLtByPipeSys`:拿 `st.sLt` 照旧去线型表解析,没有则 `loadLineType`; +- `getLwByPipeSys`:拿 `st.sLw` 照旧乘图纸比例; +- `getLyByPipeSys`:拿 `st.sLy` 照旧"没有就建图层、`bUpdate` 时刷新"。建图层时它内部还会调 `getLtByPipeSys`/`getColorByPipeSys`,但这俩现在**直接命中缓存,不再开库**,所以那段不用改。 + +`getPipeColorBySystem`(`CoreTools.cpp:4258`)后续也应改成走同一个 `GetPipeSysStyle` 缓存(当前只有 1 处调用且被注释,优先级低)。 + +## 8. 收益对比 + +| 场景 | 现在 | 改后 | +| --- | --- | --- | +| `dwgInFields` 8 个 getter(样式键缺失) | 最多 16+ 次 open + 16 条 SQL | 首次 2 次 open,之后 0 次 | +| `getCurrentPipeSysTblName` 46 处调用 | 每次 1 次 open | 首次 1 次,之后 0 次 | +| 一张图几百个实体加载 | 几千次串行 open | 约 2 次 | +| Jig 拖动每帧 | 16 次 open | 首次 2 次,之后 0 次 | + +## 9. 取舍与风险 + +| 取舍/风险 | 处理 | +| --- | --- | +| 缓存只存字符串,不存 `AcDbObjectId` | 图层/线型 id 是"当前图纸"的,多文档切换后会失效,必须每次现解析(`getLtByPipeSys`/`getLyByPipeSys` 里的图纸操作保留) | +| 改了系统样式配置后读到旧值 | 缓存生命周期 = 进程,重启软件生效。后续可在配置对话框保存后加 `InvalidatePipeSysStyleCache()` | +| 配错的系统名被永久缓存 | 查不到也缓存默认值,避免反复查库;重启即可恢复 | +| 多线程 | 按单线程实现(ARX 命令在主线程);若担心给 `s_map` 加 `CCriticalSection` | +| 内存 | 条目数 = 系统名 × 2,通常个位数,可忽略 | +| 老 preset 编译 | 用 `std::map` + C++03 语法,兼容 R18~R26 全套 preset | + +## 10. 可选第二层:常驻只读连接(暂不建议先做) + +如果想连"首次 2 次 open"也省掉,或者覆盖那 46 处不走 4 个 getter 的其它查询,可以给 `Plumbing.db` 做一个进程级常驻只读 `CUIISQLiteServer` 单例。 + +但这层风险更高,暂缓: + +- `CUIISQLiteServer` 构造/析构里绑了 `CoInitialize`/`CoUninitialize`,单例要自己管理 COM 生命周期; +- sqlite3 同一连接对象跨线程并发不安全,需要锁或每线程连接; +- 现有大量写操作依赖"用完即 close"来落盘/释放写锁,常驻连接只适合只读,写路径不能动。 + +## 11. 实施与验证 + +实施顺序: + +1. **先做第 7 节第一层**(`CoreTools.cpp` 一处改动):覆盖 192 处调用点里的全部热点,`dwgInFields` 和绘制两条路径一起解决。 +2. 用 MSVC 性能分析对比改前/改后的 `sqlite3_open` 调用次数。 +3. 功能回归: + - 管线中心线 / 外边线的颜色、线型、线宽、图层与改之前一致; + - 画一根带法兰的管段 + 弯头 + 三通,实体显示正常; + - 打开两张图纸各画一段,确认图层/线型不串图纸; + - 打开老版本图纸(样式键缺失)加载正常。 +4. 编码自检:`CoreTools.cpp` 保持 UTF-8 BOM。 + +涉及文件: + +- **修改**:`SourceCode/Code2026/CoreCommand/CoreTools.cpp`(匿名 namespace 加 `PipeSysStyle` / `QueryPipeSysStyle` / `GetPipeSysStyle`;改 `getCurrentPipeSysTblName` + 4 个 getter) +- **不动**:`inc/CoreTools.h`(函数签名不变)、`sqliteserver` 工程、`CoreLibrary` / `PipelineCmd` / `PipelineSpecCmd` 业务代码 + +## 12. 实施记录(2026-10-27) + +### 12.1 实际改动(仅 `CoreCommand/CoreTools.cpp`,+106 / -35 行) + +| 位置 | 改动 | +| --- | --- | +| `getCurrentPipeSysTblName`(约 4912) | 按 `getCurrentTrade()` 缓存表名;静态指针故意不释放,避开 DLL 卸载期析构顺序问题 | +| 新增匿名 namespace(4 个 getter 之前) | `PipeSysStyle`、`PipeSysStyleMap`、`PipeSysStyleCache()`、`QueryPipeSysStyle()`、`GetPipeSysStyle()` | +| `getColorByPipeSys` | 改为读缓存,库打不开仍返回 256(保持原语义) | +| `getLtByPipeSys` | 线型名取自缓存,后续「线型表解析 → `loadLineType`」逻辑原样保留 | +| `getLwByPipeSys` | 线宽取自缓存,后续「乘图纸比例」逻辑原样保留 | +| `getLyByPipeSys` | 图层名取自缓存,后续「建图层 / `bUpdate` 刷新」逻辑原样保留 | + +实现细节与第 7 节草案的差异: + +- 缓存容器用 `static ... *s_pMap = new ...` 的**故意泄漏**写法(而非函数内静态对象),避免 MFC 对象在 DLL 卸载阶段析构导致的崩溃风险;`getCurrentPipeSysTblName` 的缓存同样处理。 +- 保留「库打不开就早退」的原行为:`GetPipeSysStyle` 返回 `false` 时,`getColorByPipeSys` 返回 256、`getLtByPipeSys`/`getLyByPipeSys` 返回空 id、`getLwByPipeSys` 返回 0——与改动前一致。 +- `getPipeColorBySystem`(`CoreTools.cpp:4258`)**未改**。它的 SQL 不带 `PIPESYS` 过滤、取的是「任意一行」的颜色,语义与 4 个 getter 不同,改成走缓存会改变取值;目前只有 1 处调用且调用点被注释,收益低、风险高。但它复用 `getCurrentPipeSysTblName()`,已顺带省掉一次开库。 + +### 12.2 编译验证 + +R190(AutoCAD 2019,C++98,`v142`)Configure + Debug 编译通过: + +```bash +cd SourceCode/Code2026 +cmake --preset R190 +cmake --build --preset R190 --config Debug --target AecRxBase +cmake --build --preset R190 --config Debug --target CoreCommand_BAL_REMOTE +cmake --build --preset R190 --config Debug --target CoreLibrary_BAL_REMOTE +cmake --build --preset R190 --config Debug --target CoreImplement +``` + +四个目标全部链接成功(`CoreCommand_BAL_REMOTE` 内已重新编译 `CoreTools.cpp`,无新增 warning)。 + +### 12.3 待做 + +- 在 CAD 中加载 `CoreCommand.arx` 后实测:管线中心/外边线的颜色、线型、线宽、图层与改前一致;老图纸(样式键缺失)加载正常;两张图切换不串图层/线型。 +- 用 MSVC 性能分析对比 `sqlite3_open` / `sqlite3_exec` 调用次数。 +- 若后续需要「改配置立即生效」,在系统样式配置保存处加 `InvalidatePipeSysStyleCache()`(清空 `PipeSysStyleCache()`)。