8000 refact(database/gdb): refactor gdb schema caching with type-safe tableRegistry by LanceAdd · Pull Request #4711 · gogf/gf · GitHub
[go: up one dir, main page]

Skip to content

refact(database/gdb): refactor gdb schema caching with type-safe tableRegistry - #4711

Open
LanceAdd wants to merge 7 commits into
gogf:masterfrom
LanceAdd:feature/20260224
Open

LanceAdd wants to merge 7 commits into
gogf:masterfrom
LanceAdd:feature/20260224

Conversation

@LanceAdd
Copy link
Copy Markdown
Member

变更概述

本次PR对 database/gdb 模块的schema元数据缓存机制进行了全面重构,用类型安全的 tableRegistry 替代原有的 innerMemCache,解决了多个正确性、一致性、并发安全和性能问题。

主要改进

🎯 核心重构

  • 引入 tableRegistry 作为schema元数据的唯一存储源
  • 采用 (group, schema, table) 三维寻址,彻底消除跨schema缓存混淆
  • 使用 sync.RWMutex + map 的组合,提供类型安全的并发访问

🔧 问题修复

正确性问题:

  • ✅ 修复多schema场景下的表名缓存混淆
  • ✅ 为 HasTable 和 GetTablesWithCache 添加schema参数支持
  • ✅ 解决不同group间同名表的软删除字段缓存污染
  • ✅ 修正字段映射时的表存在性判断错误

一致性问题:

  • ✅ ClearTableFields 现在会清除表的所有相关缓存
  • ✅ ClearTableFieldsAll 现在真正清除所有schema元数据
  • ✅ 表名缓存现在可以被主动清除

并发安全问题:

  • ✅ 软删除字段查询不再出现cold start并发穿透

性能优化:

  • ✅ HasTable 从O(n)线性扫描优化为O(1)哈希查找
  • ✅ 消除了全局写锁导致的性能瓶颈
  • ✅ 表名批量注册从O(N)次锁操作优化为O(1)次

🏗️ 架构优化

  • 消除了冗余的独立缓存(tablenames和软删除字段)
  • 统一了缓存清除逻辑,提供完整的"按表清除所有相关缓存"能力
  • 简化了代码路径,提高了可维护性

接口变更

向后兼容的增强

// 新增可选schema参数,现有调用无需修改
HasTable(name string, schema ...string) (bool, error)
GetTablesWithCache(schema ...string) ([]string, error)

内部实现优化

  • SetTableFields 签名保持不变,生成代码无需调整
  • 移除了已废弃的 GetInnerMemCache() 方法
  • 软删除字段查询直接从TableFields派生,无需独立缓存

迁移说明

本次变更对现有用户代码完全向后兼容,无需任何代码修改即可享受性能提升和问题修复。

- 将Core结构体中的innerMemCache替换为tableRegistry以统一管理表结构元数据
- HasTable函数优化为通过tableRegistry完成表存在性判断,提高查询效率
- SetTableFields改为通过registry缓存表字段信息,移除直接操作缓存的逻辑
- GetTablesWithCache改用registry缓存表名列表以避免重复数据库查询
- ClearTableFields及ClearTableFieldsAll改为操作registry,实现缓存清理
- TableFields获取及软时间字段缓存逻辑改为访问registry,去除冗余缓存层
- 删除与innerMemCache相关的缓存键生成及无用缓存代码
- 新增tableRegistry类型,实现线程安全的三维(group-schema-table)元数据缓存管理
- 修改相关调用逻辑以适配registry缓存体系,提升缓存一致性及并发性能
- 修改 GetLoadedSchemaTables 方法以跟踪完整表名列表是否已从数据库加载
- 添加 schemaKey 类型用于标识 (group, schema) 对以跟踪表列表加载状态
- 新增 loadedSchemas 映射跟踪哪些 (group, schema) 对的完整表名列表已加载
- 更新 Sets 方法记录完整表名列表已从数据库加载的信息
- 修改 ClearTableFields 方法保留表存在标记以避免数据库往返查询
- 更新 ClearAll 方法清除已加载模式标记确保下次访问重新查询数据库
- 在 gdb_func.go 中为 HasTable 调用添加模式参数支持
Copilot AI left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the database schema metadata caching mechanism in the database/gdb module by introducing a type-safe tableRegistry to replace the generic innerMemCache. The refactoring addresses correctness issues with multi-schema support, cache consistency, concurrent safety, and performance.

Changes:

  • Introduced tableRegistry with three-dimensional addressing (group, schema, table) to replace innerMemCache for schema metadata storage
  • Enhanced HasTable and GetTablesWithCache with optional schema parameters while maintaining backward compatibility
  • Refactored soft delete field caching to derive directly from the table fields registry, eliminating a separate cache layer

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.

Show a summary per file
File Description
gdb_schema_table_registry.go New file implementing type-safe registry for schema metadata with concurrent-safe operations
gdb.go Updated Core struct to use registry instead of innerMemCache, removed obsolete cache prefix constant
gdb_core.go Refactored HasTable to use O(1) registry lookup, GetTablesWithCache to support schema parameter, removed GetInnerMemCache method
gdb_core_utility.go Updated ClearTableFields and ClearTableFieldsAll to work with registry, simplified ClearCacheAll
gdb_driver_wrapper_db.go Refactored TableFields to use registry's GetOrSet with double-checked locking, removed cache imports
gdb_func.go Updated HasTable calls to pass schema parameter, removed obsolete cache key generation functions
gdb_model_utility.go Updated HasTable call to include schema parameter for correct table existence checking
gdb_model_soft_time.go Refactored to derive soft time fields directly from registry, removed separate cache layer and related structures

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@gqcn gqcn changed the title fix(database/gdb): refactor gdb schema caching with type-safe tableRegistry refact(database/gdb): refactor gdb schema caching with type-safe tableRegistry Jul 8, 2026
@gqcn
gqcn commented Jul 8, 2026
Copy link
Copy Markdown
Member

@LanceAdd 该PR也很有意思,但影响面较大,也保持审核状态,预计在v3版本考虑合并。

@LanceAdd LanceAdd added V3 and removed V3 labels Aug 8, 2026
@gqcn
gqcn commented Sep 14, 2026 •
Copy link
Copy Markdown
Member

我还不能可靠完成这次审查,建议请维护者协助确认一下。

原因是:目标分支当前提交上读不到这次审查需要的 AGENTS.md,没法按仓库规范核对代码。

建议关注:gdb schema 缓存重构;标题用了 refact 而不是 refactor。

如果方便的话,建议请以下成员协助:@wln32 @xgd16 @jianchenma

提及原因:这些成员处理过相关文件。

我暂时没有添加 bot-approved 标签。

@LanceAdd LanceAdd added the V3 label Sep 23, 2026
@gqcn
gqcn commented Oct 8, 2026
Copy link
Copy Markdown
Member

表结构按 group/schema/table 收进 tableRegistry,多 schema 串缓存这件事是对的。不过有几处结果和现在不一样,HasTable 会把不存在的表说成存在。

database/gdb/gdb_core_utility.go:148 的 ClearTableFields 用 registry.Set(..., nil) 清字段。表还不在 registry 里时,这会新插一条存在标记。HasTable 看到 key 就直接返回 true,不再查库。ClearTableFields(ctx, "not_a_table") 之后,HasTable("not_a_table") 会得到 true。key 已经存在时再把字段置空就行,不存在就不要写进去。

database/gdb/gdb_core.go:787 的 GetTablesWithCache 第一次返回的是数据库里的表名,Sets 随手把这个 schema 标成已加载。下一次走 GetLoadedSchemaTables,扫的是 registry 里这个 group/schema 的全部 key,顺序跟第一次不同,SetTableFields、TableFields 以及上面误插入的名字也会混进来。表名列表单独留一份加载快照,别和字段缓存放同一个 map。schema 还没加载时这里也没有 singleflight,并发的 HasTable 会一起打到 Tables()。

database/gdb/gdb_schema_table_registry.go:135 的 GetOrSet 在跑 loader 时一直握着 registry 写锁。一次慢的 TableFields 会把同一个 Core 上其他表的字段查询、HasTable 和清缓存都堵住。查询放到锁外面,按表做 singleflight。

gdb_schema_table_registry.go 没有配套测试。清一个不存在的表、空库加载后再取表名、并发第一次加载,这几条用 gtest 就能罩住。

标题类型写成了 refact,约定里没有这个词,改成 refactor。GetInnerMemCache 现在是导出方法,在 v2 上直接删掉的话,外面已有调用会编译失败。这批如果要等 v3 再合,master 上先留一个废弃包装。

@gqcn
gqcn commented Oct 12, 2026
Copy link
Copy Markdown
Member

按 group/schema/table 把表结构收进 tableRegistry,多 schema 串缓存这个方向是对的。后面几次提交只是把 master 合进来,上次说的结果差异还在:HasTable 仍会把库里没有的表说成存在。

database/gdb/gdb_core_utility.go:148 的 ClearTableFields 用 registry.Set(..., nil) 清字段。表还不在 registry 里时,这会新插一条存在标记。HasTable(gdb_core.go:747)看到 key 就直接返回 true,不再查库。ClearTableFields(ctx, "not_a_table") 之后,HasTable("not_a_table") 会得到 true。key 已经存在时再把字段置空就行,不存在就不要写进去。

SetTableFields 也写进同一个 map。dao 启动注入字段之后,HasTable 不再对照数据库,表还没建出来或者已经被删掉也会返回 true,字段映射会把它当成真实表。GetTablesWithCache(gdb_core.go:794)第一次返回的是 Tables() 的结果,Sets 顺手把这个 schema 标成已加载;下一次走 GetLoadedSchemaTables(gdb_schema_table_registry.go:97),扫的是 registry 里这个 group/schema 的全部 key,顺序跟第一次不同,SetTableFields 和上面误插入的名字也会混进来。表名列表单独留一份加载快照,别和字段缓存放同一个 map。schema 还没加载时这里也没有 singleflight,并发的 HasTable 会一起打到 Tables()。

GetOrSet(gdb_schema_table_registry.go:148)跑 loader 时一直握着 registry 写锁。一次慢的 TableFields 会把同一个 Core 上其他表的字段查询、HasTable 和清缓存都堵住。查询放到锁外面,按表做 singleflight。软删除字段改成每次从字段 map 现算(gdb_model_soft_time.go:157),每次拼条件都会再扫字段并做类型判断,原来命中缓存后只是取值。解析结果跟这张表的字段一起留下,避免把这块算进每次请求。

gdb_schema_table_registry.go 还是没有配套测试。清一个不存在的表、SetTableFields 之后再 HasTable、空库加载后再取表名、并发第一次加载,这几条用 gtest 就能罩住。

标题类型写成了 refact,模板里的类型是 refactor。GetInnerMemCache 是导出方法,在 v2 的 master 上直接删掉,外面已有调用会编译失败。这批如果要等 v3 再合,master 上先留一个废弃包装。

这几处还没改完,我没有打 bot-approved。

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

0