数据库元数据查询:sqlite_master表、PRAGMA命令、获取表结构信息、获取数据库版本

做嵌入式数据库开发,说白了就是跟数据打交道。但很多时候,我们不光要操作数据,还得搞清楚数据库本身长什么样——有哪些表?表里有哪些字段?字段是什么类型?数据库版本是多少?这些信息,就是所谓的「元数据」。

我个人习惯,在接手一个遗留项目时,第一件事就是先查元数据。为什么?因为你不知道前任开发者留下了什么坑。有一次我调试一个工控设备上的SQLite数据库,程序老是崩溃,查了半天才发现有个表的结构跟代码里写的不一致。从那以后,我写任何数据库工具,都会先加一段元数据查询的代码。

sqlite_master:数据库的「户口本」

SQLite内部维护了一张特殊的表,叫sqlite_master。这张表记录了数据库中所有对象的信息——表、索引、视图、触发器,一个不落。

你可以像查普通表一样查它:

SELECT name, type, sql FROM sqlite_master ORDER BY type, name;

返回的结果大概长这样:

nametypesql
sensor_datatableCREATE TABLE sensor_data(id INTEGER PRIMARY KEY, temp REAL, humidity REAL, ts TEXT)
idx_tempindexCREATE INDEX idx_temp ON sensor_data(temp)
v_avgviewCREATE VIEW v_avg AS SELECT AVG(temp) FROM sensor_data

sql字段里存的是创建该对象时的完整SQL语句。嗯,这里要注意:如果你用ALTER TABLE改过表结构,sql字段里存的还是最初的CREATE语句,不会自动更新。我曾经因为这个踩过坑——查到的字段列表跟实际对不上,排查了半天才发现是这个问题。

如果你想只看表,不看索引和视图,加个条件就行:

SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;
小技巧:SQLite 3.33.0之后,还多了一张sqlite_schema表,功能跟sqlite_master完全一样。我个人习惯用sqlite_master,兼容性更好。

PRAGMA命令:数据库的「控制面板」

PRAGMA是SQLite特有的一组命令,用来查询和设置数据库的内部参数。说白了,它就像数据库的控制面板——你能看到版本、编码、页大小、外键开关等等。

常用的PRAGMA查询命令:

-- 获取数据库版本
PRAGMA schema_version;

-- 获取应用层版本(用户自定义)
PRAGMA user_version;

-- 获取数据库编码
PRAGMA encoding;

-- 获取页大小
PRAGMA page_size;

-- 查看外键约束是否开启
PRAGMA foreign_keys;

你想想看,这些信息在调试时多有用。有一次我遇到一个数据库文件打不开,一查PRAGMA schema_version,发现版本号异常,原来是文件头损坏了。如果没有这个命令,我可能还在傻乎乎地重装驱动。

注意:PRAGMA命令的返回值在不同SQLite版本中可能略有差异。比如PRAGMA encoding在旧版本中返回的是字符串,新版本中返回的是整数编码。写跨版本代码时一定要测试。

获取表结构信息:PRAGMA table_info

这是我最常用的PRAGMA命令之一。想知道一个表有哪些字段、什么类型、能不能为空?一句搞定:

PRAGMA table_info(sensor_data);

返回结果:

cidnametypenotnulldflt_valuepk
0idINTEGER1NULL1
1tempREAL0NULL0
2humidityREAL0NULL0
3tsTEXT0CURRENT_TIMESTAMP0

字段含义:

  • cid:列编号,从0开始
  • name:字段名
  • type:字段类型(SQLite是弱类型,这里只是声明类型)
  • notnull:是否非空,1表示不能为空
  • dflt_value:默认值
  • pk:是否主键,1表示是主键

在C代码里怎么用?我一般这样写:

sqlite3_stmt *stmt;
const char *sql = "PRAGMA table_info(sensor_data)";
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);

while (sqlite3_step(stmt) == SQLITE_ROW) {
    int cid = sqlite3_column_int(stmt, 0);
    const char *name = sqlite3_column_text(stmt, 1);
    const char *type = sqlite3_column_text(stmt, 2);
    int notnull = sqlite3_column_int(stmt, 3);
    int pk = sqlite3_column_int(stmt, 5);
    printf("字段: %s, 类型: %s, 主键: %d\n", name, type, pk);
}
sqlite3_finalize(stmt);

这段代码我几乎每个项目都会用到。你把它封装成一个函数,以后查任何表的结构,一行调用就行。

获取数据库版本:不止一种方法

SQLite的版本信息可以从多个地方获取,我整理了一下:

方法代码返回内容
SQL函数SELECT sqlite_version();版本号字符串,如"3.40.0"
编译时宏SQLITE_VERSION编译时的版本号
运行时APIsqlite3_libversion()运行时链接的版本号
PRAGMAPRAGMA schema_version;数据库文件的schema版本号(整数)

这里有个坑:sqlite_version()sqlite3_libversion()返回的是SQLite库的版本,而PRAGMA schema_version返回的是数据库文件的内部版本号。前者用于判断功能支持,后者用于判断数据库文件是否需要迁移。

我曾经在一个项目中,用PRAGMA schema_version来判断数据库是否需要升级,结果发现不同设备上的版本号对不上。后来才明白,这个版本号是每次DDL操作(比如CREATE TABLE)时自动递增的,不是我们通常理解的「数据库版本」。所以后来我改用PRAGMA user_version来存自定义版本号,这才彻底解决问题。

最佳实践:在C代码中获取SQLite版本,建议用sqlite3_libversion(),因为它不需要执行SQL语句,效率更高。而且它返回的是运行时实际链接的版本,不是编译时的版本——这两者可能不一样,尤其是在动态链接的场景下。

知识体系总览

下面这张图,把本章涉及的元数据查询方式串起来了。你一看就明白:

SQLite元数据查询 sqlite_master表 列出所有表/索引/视图 获取CREATE语句原文 过滤 type='table' 查表名 PRAGMA命令 schema_version / user_version encoding / page_size foreign_keys 等参数 PRAGMA table_info 字段名 / 类型 / 主键 是否非空 / 默认值 C代码中遍历结果集 获取数据库版本 sqlite_version() SQL函数 sqlite3_libversion() C API PRAGMA user_version 自定义版本号(推荐) 用于数据库迁移判断

这张图把本章的核心内容都串起来了。你写代码的时候,可以把它当成一个速查地图——需要查什么元数据,直接找对应的分支就行。

最后说一句:元数据查询看似基础,但用好它,能让你少踩很多坑。我每次写数据库工具,都会先封装一套元数据查询函数。你想想看,如果连数据库里有什么表、表里有什么字段都不清楚,你怎么敢放心地读写数据?


公众号:蓝海资料掘金营,微信deep3321