数据库元数据查询:sqlite_master表、PRAGMA命令、获取表结构信息、获取数据库版本
做嵌入式数据库开发,说白了就是跟数据打交道。但很多时候,我们不光要操作数据,还得搞清楚数据库本身长什么样——有哪些表?表里有哪些字段?字段是什么类型?数据库版本是多少?这些信息,就是所谓的「元数据」。
我个人习惯,在接手一个遗留项目时,第一件事就是先查元数据。为什么?因为你不知道前任开发者留下了什么坑。有一次我调试一个工控设备上的SQLite数据库,程序老是崩溃,查了半天才发现有个表的结构跟代码里写的不一致。从那以后,我写任何数据库工具,都会先加一段元数据查询的代码。
sqlite_master:数据库的「户口本」
SQLite内部维护了一张特殊的表,叫sqlite_master。这张表记录了数据库中所有对象的信息——表、索引、视图、触发器,一个不落。
你可以像查普通表一样查它:
SELECT name, type, sql FROM sqlite_master ORDER BY type, name;
返回的结果大概长这样:
| name | type | sql |
|---|---|---|
| sensor_data | table | CREATE TABLE sensor_data(id INTEGER PRIMARY KEY, temp REAL, humidity REAL, ts TEXT) |
| idx_temp | index | CREATE INDEX idx_temp ON sensor_data(temp) |
| v_avg | view | CREATE 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_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 encoding在旧版本中返回的是字符串,新版本中返回的是整数编码。写跨版本代码时一定要测试。
获取表结构信息:PRAGMA table_info
这是我最常用的PRAGMA命令之一。想知道一个表有哪些字段、什么类型、能不能为空?一句搞定:
PRAGMA table_info(sensor_data);
返回结果:
| cid | name | type | notnull | dflt_value | pk |
|---|---|---|---|---|---|
| 0 | id | INTEGER | 1 | NULL | 1 |
| 1 | temp | REAL | 0 | NULL | 0 |
| 2 | humidity | REAL | 0 | NULL | 0 |
| 3 | ts | TEXT | 0 | CURRENT_TIMESTAMP | 0 |
字段含义:
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 | 编译时的版本号 |
| 运行时API | sqlite3_libversion() | 运行时链接的版本号 |
| PRAGMA | PRAGMA schema_version; | 数据库文件的schema版本号(整数) |
这里有个坑:sqlite_version()和sqlite3_libversion()返回的是SQLite库的版本,而PRAGMA schema_version返回的是数据库文件的内部版本号。前者用于判断功能支持,后者用于判断数据库文件是否需要迁移。
我曾经在一个项目中,用PRAGMA schema_version来判断数据库是否需要升级,结果发现不同设备上的版本号对不上。后来才明白,这个版本号是每次DDL操作(比如CREATE TABLE)时自动递增的,不是我们通常理解的「数据库版本」。所以后来我改用PRAGMA user_version来存自定义版本号,这才彻底解决问题。
sqlite3_libversion(),因为它不需要执行SQL语句,效率更高。而且它返回的是运行时实际链接的版本,不是编译时的版本——这两者可能不一样,尤其是在动态链接的场景下。
知识体系总览
下面这张图,把本章涉及的元数据查询方式串起来了。你一看就明白:
这张图把本章的核心内容都串起来了。你写代码的时候,可以把它当成一个速查地图——需要查什么元数据,直接找对应的分支就行。
最后说一句:元数据查询看似基础,但用好它,能让你少踩很多坑。我每次写数据库工具,都会先封装一套元数据查询函数。你想想看,如果连数据库里有什么表、表里有什么字段都不清楚,你怎么敢放心地读写数据?
公众号:蓝海资料掘金营,微信deep3321