99、数据库编程最佳实践:编码规范、性能模式、错误处理模式、设计模式

做了这么多年嵌入式数据库开发,我踩过的坑比写过的代码还多。今天聊的这几个话题——编码规范、性能模式、错误处理、设计模式——说白了就是让你少走弯路。每个都是我用实际项目换来的教训,你仔细看看,肯定用得上。

一、编码规范:看着舒服,跑着稳当

我见过最头疼的代码,就是那种变量名随便起、缩进乱七八糟的。你想想看,一个团队里如果有人乱写,后期维护简直是噩梦。

命名规则

  • 表名:小写+下划线,比如 user_infoorder_detail
  • 字段名:同样小写+下划线,比如 create_timeis_active
  • C语言变量:我习惯用 tbl_ 前缀表示表结构,p_ 表示指针参数

核心原则:命名要自解释。看到名字就知道它存什么、干什么用。

代码布局

/* 好的例子 */
static int db_insert_user(sqlite3 *db, const char *name, int age)
{
    int rc;
    sqlite3_stmt *stmt = NULL;

    const char *sql = "INSERT INTO user(name, age) VALUES(?, ?)";
    rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
    if (rc != SQLITE_OK) {
        return rc;
    }

    sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC);
    sqlite3_bind_int(stmt, 2, age);

    rc = sqlite3_step(stmt);
    sqlite3_finalize(stmt);
    return rc;
}

我个人习惯在函数之间空两行,逻辑块之间空一行。别小看这些细节,读代码的时候舒服多了。

二、性能模式:别让数据库拖后腿

嵌入式设备资源有限,数据库性能优化是必修课。我在一个物联网项目里遇到过,数据量才几万条,查询却慢得像蜗牛。后来一查,索引没建对。

索引使用策略

场景 建议 原因
频繁查询的字段 建索引 加速检索
经常排序的字段 建索引 避免文件排序
写多读少的表 少建索引 索引维护有开销
小表(<1000行) 不建索引 全表扫描更快

小技巧:用 EXPLAIN 看查询计划。我每次写复杂查询都会跑一遍,看看有没有走全表扫描。

批量操作

逐条插入数据?千万别。用事务包起来,性能能提升几十倍。

/* 慢的方式 */
for (int i = 0; i < 10000; i++) {
    sqlite3_exec(db, "INSERT INTO t VALUES(1)", NULL, NULL, NULL);
}

/* 快的方式 */
sqlite3_exec(db, "BEGIN", NULL, NULL, NULL);
for (int i = 0; i < 10000; i++) {
    sqlite3_exec(db, "INSERT INTO t VALUES(1)", NULL, NULL, NULL);
}
sqlite3_exec(db, "COMMIT", NULL, NULL, NULL);

嗯,这里要注意:事务别开太大,否则回滚会很慢。我一般控制在1000条左右一个事务。

三、错误处理模式:别让程序静默死掉

我曾经犯过一个低级错误——没检查返回值。结果程序跑着跑着突然挂了,查了半天才发现是数据库磁盘满了。

错误处理三原则

  1. 检查每个API的返回值:SQLite的每个函数都有返回码,别偷懒
  2. 区分致命错误和可恢复错误:磁盘满可以重试,语法错误直接报
  3. 记录错误上下文:光说"数据库错误"没用,要带上SQL语句和错误码

警告:千万不要在错误处理里直接 exit(1)。嵌入式设备可能需要优雅降级,比如切换到只读模式。

/* 正确的错误处理模式 */
int safe_query(sqlite3 *db, const char *sql)
{
    int rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
    if (rc != SQLITE_OK) {
        /* 记录详细错误 */
        fprintf(stderr, "[DB_ERROR] sql=%s, rc=%d, msg=%s\n",
                sql, rc, sqlite3_errmsg(db));

        /* 根据错误码决定处理方式 */
        if (rc == SQLITE_FULL) {
            /* 磁盘满,尝试清理旧数据 */
            return clean_old_data(db);
        } else if (rc == SQLITE_BUSY) {
            /* 锁冲突,重试 */
            return retry_query(db, sql, 3);
        }
        return rc;
    }
    return SQLITE_OK;
}

四、设计模式:架构稳了,代码就稳了

数据库编程里,我用的最多的模式就三个。每个都是实战中提炼出来的。

1. 单例模式

嵌入式设备通常只有一个数据库连接。用单例模式管理,避免重复打开关闭。

static sqlite3 *g_db = NULL;

sqlite3* get_db_instance(void)
{
    if (g_db == NULL) {
        sqlite3_open("/data/app.db", &g_db);
    }
    return g_db;
}

2. 仓储模式

把数据库操作封装成独立的模块。业务逻辑不直接写SQL,而是调用仓储函数。

/* 仓储层 */
int user_repo_insert(const user_t *user)
{
    /* 内部处理SQL和参数绑定 */
}

int user_repo_find_by_id(int id, user_t *out)
{
    /* 查询逻辑封装在这里 */
}

这样做的好处是:哪天换数据库了,只改仓储层就行。我在一个项目里从SQLite换到Berkeley DB,就改了三个文件。

3. 观察者模式

数据变更时通知其他模块。比如配置表改了,自动刷新缓存。

typedef void (*db_change_cb)(const char *table_name);

static db_change_cb callbacks[10];
static int cb_count = 0;

void db_register_callback(db_change_cb cb)
{
    callbacks[cb_count++] = cb;
}

void db_notify_change(const char *table)
{
    for (int i = 0; i < cb_count; i++) {
        callbacks[i](table);
    }
}

知识体系总览

下面这张图把今天讲的内容串起来了。你对照着看,心里就有谱了。

数据库编程最佳实践 编码规范 命名规则 代码布局 注释规范 性能模式 索引策略 批量操作 事务控制 错误处理 返回值检查 错误分级 日志记录 设计模式 单例模式 仓储模式 观察者模式 规范 + 性能 + 容错 + 架构 = 可靠系统

说实话,这些最佳实践不是一天练成的。我刚开始写数据库代码时也踩过不少坑。但只要你坚持用这些模式,代码质量会肉眼可见地提升。记住:好的代码不是写出来的,是改出来的。

最后提醒一句:别想着一次把所有模式都用上。先挑一个你最薄弱的环节,比如错误处理,把它做到位。其他的慢慢来,稳扎稳打。

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