Db::getFields() 底层执行 SHOW FULL COLUMNS FROM table_name(MySQL),缓存键为 database.table_name.fields,不支持视图,类型映射依赖驱动且存在版本差异。

ThinkPHP 的 Db::getFields() 底层到底查了什么
它不执行 SHOW COLUMNS,也不走 INFORMATION_SCHEMA(至少默认不走)。ThinkPHP 6+ 实际调用的是 PDO 的 getColumnMeta() —— 但这个函数在 MySQL 驱动下是**不支持的**,所以框架会 fallback 到自己拼的 SHOW FULL COLUMNS FROM `table_name` 查询。
这意味着:只要数据库用户有 SELECT 权限(哪怕没权限查 INFORMATION_SCHEMA),Db::getFields() 就能跑通;但如果禁用了 SHOW 语句(某些云数据库或安全策略),就会直接报错 SQLSTATE[HY000]: General error: 1142 SHOW command denied。
- 真实执行的 SQL 是:
SHOW FULL COLUMNS FROM `user`(带反引号,表名来自当前查询的模型或显式传入) - 返回字段含
name、type、notnull、default、extra(比如auto_increment)等,但不包含索引或外键信息 - 如果表不存在,会抛出异常
think\db\exception\DataNotFoundException,不是静默返回空数组
Db::getFields() 的缓存机制和手动刷新方式
它默认走「数据表结构缓存」,不是查询缓存。缓存键是 database.table_name.fields,存储在配置的缓存驱动中(如 file、redis),有效期为永久(除非手动清除或缓存过期)。
问题来了:改了表结构,Db::getFields() 还返回旧字段 —— 因为缓存没更新。ThinkPHP 不监听 DDL 变更,也不会自动失效。
立即学习“PHP免费学习笔记(深入)”;
- 清缓存最直接的方式:
Cache::tag('schema')->clear()(前提是建表时用了 schema 标签,TP6 默认启用) - 更稳妥的手动刷新:
Db::getFields('user', true),第二个参数$refresh = true强制绕过缓存重查 - 开发环境可关掉缓存:在
database.php中设'schema_cache' => false,但线上不建议
不同数据库驱动下 getFields() 的行为差异
MySQL 和 PostgreSQL 表现接近,SQLite 差异最大:它没有原生 SHOW COLUMNS,ThinkPHP 会查 PRAGMA table_info('table_name'),返回字段少(比如没有 extra 或 comment),type 字段也只返回基础类型(INTEGER 而非 INT UNSIGNED)。
- SQL Server 使用
sys.columns+sys.types关联查询,字段名映射为name→column_name,需注意大小写敏感性 - Oracle 需要
ALL_TAB_COLUMNS视图权限,且表名默认大写,传参时得写Db::getFields('USER')而非'user' - 所有驱动下,
getFields()都不支持视图(会报错或返回空),只认物理表
别把 getFields() 当字段校验工具用
它返回的是「当前时刻数据库里定义的字段」,不是模型类里声明的验证规则,也不是运行时实际 select 出来的字段。有人拿它做动态表单生成,结果发现 json 类型字段在 MySQL 5.7+ 返回 json,8.0+ 却返回 longtext —— 因为底层 SHOW FULL COLUMNS 对 JSON 类型的描述不一致。
- 字段类型映射由驱动内部硬编码决定,比如
tinyint(1)自动转成boolean,但tinyint(2)就还是integer,容易误判布尔字段 - 如果表用了列别名或生成列(generated column),
getFields()不会体现,它只读表定义 - 想确保字段存在且可用,比对
getFields()结果只是第一步;真正要用,还得配合try/catch捕获后续查询异常
缓存键名、驱动差异、类型映射规则——这三块最容易在换环境或升级数据库后突然出问题,改之前最好先看一眼 cache/schema/ 下的缓存文件内容或连 redis 查下 key。


















