SQLite数据库基础:特点、创建与约束

各位同学,今天我们来聊聊SQLite。说实话,我第一次接触SQLite是在做一个嵌入式项目的时候。当时要在资源受限的设备上存数据,MySQL太重了,文件型数据库又不够用。后来同事推荐了SQLite,一试就爱上了。嗯,今天我就把这些年积累的经验分享给大家。

SQLite的独特之处

SQLite不是那种需要安装服务的数据库。它就是一个库文件,直接嵌入到你的程序里。我经常跟新人说:SQLite就是数据库界的瑞士军刀——小巧、实用、不需要额外配置。

它的核心特点,我总结为以下几点:

  • 零配置:不需要安装、不需要管理员、不需要启动服务。你调用API,它就开始工作。
  • 单文件存储:整个数据库就是一个文件。我在项目中经常用它做数据交换格式,比XML和JSON方便多了。
  • 轻量级:库文件只有几百KB。你想想看,Qt程序里嵌入一个SQLite,几乎不增加体积。
  • 事务完整:支持ACID事务。别因为它小就小看它,该有的功能一个不少。
  • 跨平台:Windows、Linux、macOS、Android、iOS都能跑。我曾在树莓派上跑过,稳得很。

适用场景:移动应用、嵌入式设备、桌面软件、测试环境、中小型网站(读多写少的情况)。

不适用场景:高并发写入、多用户同时写、数据量超过TB级别。

创建数据库——就这么简单

在Qt里操作SQLite,第一步就是创建数据库。说白了,就是指定一个文件路径。如果文件不存在,SQLite会自动创建。

// 创建/打开SQLite数据库
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
db.setDatabaseName("myapp.db");  // 数据库文件名

if (!db.open()) {
    qDebug() << "数据库打开失败:" << db.lastError().text();
    return;
}

// 此时数据库文件已经创建好了
qDebug() << "数据库创建成功!";

我个人习惯把数据库文件放在应用程序的数据目录下。曾经有个项目,我把数据库文件直接放在程序目录,结果用户没有写权限,折腾了半天。后来统一用QStandardPaths获取用户数据目录,再也没出过问题。

小技巧:如果你只想在内存中操作数据,可以用:memory:作为数据库名。程序退出后数据自动消失,适合做临时计算。

数据类型——SQLite的灵活之处

SQLite的数据类型和其他数据库不太一样。它只有5种存储类(Storage Class),但支持灵活的类型亲和性(Type Affinity)。

存储类 说明 示例
NULL 空值 NULL
INTEGER 有符号整数(1-8字节) 42, -100, 0
REAL 浮点数(8字节IEEE) 3.14, -0.001
TEXT 字符串(UTF-8/16) 'Hello', "张三"
BLOB 二进制数据 图片、文件内容

这里有个坑,我必须要提醒大家:SQLite对类型检查很宽松。你可以在INTEGER列里插入字符串,它不会报错。为什么会这样?因为SQLite采用的是动态类型系统。我记得有一次,同事往年龄字段里存了"十八岁"这个字符串,查询时做排序,结果全乱了。所以,写代码时一定要自己做好类型校验。

创建表——实战演练

创建表的语法和其他SQL数据库基本一致。我们以用户信息表为例:

CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    username TEXT NOT NULL UNIQUE,
    password TEXT NOT NULL,
    email TEXT,
    age INTEGER DEFAULT 0,
    created_at TEXT DEFAULT (datetime('now', 'localtime'))
);

这段代码里,我用了IF NOT EXISTS。为什么?因为程序可能多次启动,每次启动都创建表会报错。加上这个判断,表存在就跳过,省心。

在Qt中执行建表语句:

QSqlQuery query;
QString sql = "CREATE TABLE IF NOT EXISTS users ("
              "id INTEGER PRIMARY KEY AUTOINCREMENT, "
              "username TEXT NOT NULL UNIQUE, "
              "password TEXT NOT NULL, "
              "email TEXT, "
              "age INTEGER DEFAULT 0, "
              "created_at TEXT DEFAULT (datetime('now', 'localtime'))"
              ")";

if (!query.exec(sql)) {
    qDebug() << "建表失败:" << query.lastError().text();
} else {
    qDebug() << "表创建成功!";
}

注意:SQLite的AUTOINCREMENT和MySQL的AUTO_INCREMENT不同。SQLite中,如果不指定AUTOINCREMENT,主键也会自增,但可能会复用已删除的ID。加上AUTOINCREMENT后,ID只会递增,不会复用。不过,这会带来一点点性能开销。

约束条件——保证数据质量

约束条件就是给数据加规矩。没有规矩,不成方圆。我在项目中见过太多因为缺少约束导致的数据混乱。

常用的约束条件有这些:

  • PRIMARY KEY:主键,唯一标识一条记录。每个表只能有一个。
  • NOT NULL:字段不能为空。比如用户名,必须得有。
  • UNIQUE:字段值必须唯一。比如邮箱、手机号。
  • DEFAULT:默认值。插入数据时如果不指定,就用这个值。
  • CHECK:检查条件。比如年龄必须大于0。
  • FOREIGN KEY:外键,关联其他表。SQLite默认不开启外键约束,需要手动启用。

来看一个带完整约束的例子:

CREATE TABLE orders (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    user_id INTEGER NOT NULL,
    product_name TEXT NOT NULL,
    quantity INTEGER CHECK(quantity > 0),
    price REAL CHECK(price >= 0),
    status TEXT DEFAULT 'pending',
    FOREIGN KEY (user_id) REFERENCES users(id)
);

-- 启用外键约束(每次连接都需要执行)
PRAGMA foreign_keys = ON;

这里我用了CHECK约束来限制数量和价格。曾经有个电商项目,因为没加CHECK约束,用户下了个数量为-1的订单,导致库存计算全乱了。从那以后,我但凡涉及数值字段,都会加上CHECK。

知识体系总览

下面这张图,是我梳理的SQLite基础知识点结构。你可以把它当作学习地图:

SQLite数据库基础 核心特点 零配置 · 单文件 轻量级 · 跨平台 事务完整 创建数据库 QSqlDatabase :memory: 内存模式 文件路径管理 数据类型 NULL · INTEGER REAL · TEXT BLOB 创建表 CREATE TABLE IF NOT EXISTS 字段定义 约束条件 PRIMARY KEY NOT NULL · UNIQUE DEFAULT · CHECK FOREIGN KEY Qt集成实践 QSqlQuery执行 错误处理 PRAGMA配置

这张图把SQLite基础分成了6个模块。你从中心出发,沿着每个分支往下走,就能把知识点串起来。我个人建议的学习顺序是:先理解特点,再动手创建数据库和表,然后掌握数据类型和约束,最后在Qt中实践。

避坑指南

最后,分享几个我踩过的坑:

  • 路径问题:数据库文件路径不要写死。用QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)获取可写目录。
  • 并发写入:SQLite支持多线程读,但写操作是串行的。高并发场景下,写入会排队。我曾经有个日志系统,每秒写几百条,结果卡得不行。后来改成批量写入,问题解决。
  • 事务优化:批量插入数据时,一定要用事务包裹。不加事务的话,每条插入都会触发一次磁盘写入,速度慢10倍以上。
  • VACUUM命令:SQLite删除数据后,文件大小不会自动缩小。定期执行VACUUM可以回收空间。

好了,SQLite的基础知识就讲到这里。这些内容虽然基础,但都是我在实际项目中反复验证过的。你把这些吃透了,后面做Qt数据库集成就会顺手很多。

可可精品资料库