作为一名长期在Linux环境下开发的后端工程师,我经常需要处理各种轻量级数据存储需求。SQLite以其零配置、单文件存储的特性,成为嵌入式设备和本地应用开发的首选数据库。今天我就来分享一下在Linux系统中使用SQLite的完整实践指南。
SQLite与传统数据库最大的区别在于它不需要独立的服务进程,整个数据库就是一个普通的磁盘文件,却支持完整的SQL语法。这种设计使得它在IoT设备、移动应用和桌面程序中广泛应用。比如我最近开发的智能家居控制系统,就使用SQLite来存储设备状态和用户配置。
在Ubuntu/Debian系统上安装SQLite只需要三条命令:
bash复制sudo apt-get update
sudo apt-get install sqlite3 libsqlite3-dev sqlitebrowser
这里特别说明一下各组件作用:
安装完成后,通过sqlite3 test.db命令即可创建或打开数据库文件。这里有个实用技巧:如果只输入sqlite3而不指定文件名,会创建一个内存数据库,所有操作在退出后消失,适合临时测试。
注意:在生产环境中务必指定.db文件路径,否则数据无法持久化。我曾有一次调试时忘记指定文件名,花了半天时间录入的测试数据在退出时全部丢失。
创建表是数据库设计的核心环节。先看一个学生管理系统的表创建示例:
sql复制CREATE TABLE students (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
gender TEXT CHECK(gender IN ('男','女')),
age INTEGER CHECK(age > 0 AND age < 120),
score REAL DEFAULT 0.0,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
这里有几个关键设计要点:
实际开发中我建议使用更详细的注释:
sql复制-- 学生基本信息表
-- 创建人:张三
-- 最后修改:2023-08-20
CREATE TABLE students (
...
);
sql复制-- 单条插入(明确指定字段)
INSERT INTO students (name, gender, age)
VALUES ('王小明', '男', 18);
-- 批量插入(效率更高)
INSERT INTO students (name, gender, age, score)
VALUES
('李小红', '女', 17, 89.5),
('张大力', '男', 19, 92.0),
('刘小花', '女', 18, 76.5);
批量插入能显著提高性能。在我的性能测试中,单条插入1000条数据需要2.3秒,而批量插入仅需0.15秒。
sql复制-- 条件更新
UPDATE students SET score = score + 5 WHERE score < 60;
-- 多字段更新
UPDATE students SET
age = age + 1,
score = CASE
WHEN score > 90 THEN 100
ELSE score + 2
END
WHERE gender = '女';
sql复制-- 条件删除(务必先SELECT确认)
DELETE FROM students WHERE id = 1005;
-- 清空表(两种方式)
DELETE FROM students; -- 保留自增计数器
TRUNCATE TABLE students; -- 重置自增计数器
重要提醒:执行DELETE前务必先使用相同条件的SELECT确认目标数据。我有次误操作差点清空生产环境表,幸亏有备份。
sql复制-- 常用查询示例
SELECT id, name FROM students WHERE age >= 18;
-- 去重查询
SELECT DISTINCT gender FROM students;
-- 限制返回数量
SELECT * FROM students LIMIT 10 OFFSET 5; -- 第6-15条记录
sql复制-- 聚合函数
SELECT
gender,
COUNT(*) AS count,
AVG(score) AS avg_score,
MAX(score) AS max_score,
MIN(score) AS min_score
FROM students
GROUP BY gender;
-- 多条件查询
SELECT name FROM students
WHERE age BETWEEN 18 AND 20
AND score > 80
AND name LIKE '张%'
ORDER BY score DESC;
假设我们还有课程表和成绩表:
sql复制-- 课程表
CREATE TABLE courses (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
credit INTEGER
);
-- 成绩表
CREATE TABLE scores (
student_id INTEGER REFERENCES students(id),
course_id INTEGER REFERENCES courses(id),
score REAL,
PRIMARY KEY (student_id, course_id)
);
执行复杂查询:
sql复制-- 查询每个学生的平均分
SELECT s.name, AVG(sc.score) as avg_score
FROM students s
LEFT JOIN scores sc ON s.id = sc.student_id
GROUP BY s.id
HAVING avg_score > 75;
-- 查询选修'数据库'课程的学生
SELECT s.name, c.name as course_name, sc.score
FROM students s
JOIN scores sc ON s.id = sc.student_id
JOIN courses c ON sc.course_id = c.id
WHERE c.name = '数据库'
ORDER BY sc.score DESC;
SQLite最特别的是它的动态类型系统,字段类型只是建议而非强制:
sql复制-- 可以往INTEGER字段插入TEXT
INSERT INTO students (id, name) VALUES ('abc123', 12345);
-- 使用typeof()函数检查实际类型
SELECT id, typeof(id), name, typeof(name) FROM students;
这种灵活性有利有弊:
建议在创建表时仍明确指定类型,并在应用层做数据校验。
SQLite支持完整的事务特性:
sql复制BEGIN TRANSACTION;
INSERT INTO students (name) VALUES ('张三');
UPDATE accounts SET balance = balance - 100 WHERE user = '张三';
COMMIT; -- 或 ROLLBACK;
事务能保证操作的原子性。我曾遇到断电导致数据不一致的情况,后来所有写操作都放在事务中执行。
SQLite的C接口设计非常简洁,主要使用以下几个函数:
c复制sqlite3 *db;
int rc = sqlite3_open("test.db", &db);
if (rc != SQLITE_OK) {
fprintf(stderr, "无法打开数据库: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}
c复制char *err_msg = NULL;
char *sql = "INSERT INTO students (name) VALUES ('李四');";
rc = sqlite3_exec(db, sql, NULL, NULL, &err_msg);
if (rc != SQLITE_OK) {
fprintf(stderr, "SQL错误: %s\n", err_msg);
sqlite3_free(err_msg);
}
c复制int callback(void *data, int argc, char **argv, char **col_names) {
for (int i = 0; i < argc; i++) {
printf("%s = %s\n", col_names[i], argv[i] ? argv[i] : "NULL");
}
return 0;
}
char *select_sql = "SELECT * FROM students;";
rc = sqlite3_exec(db, select_sql, callback, NULL, &err_msg);
对于频繁执行的SQL,预处理语句更高效安全:
c复制sqlite3_stmt *stmt;
const char *sql = "INSERT INTO students (name, age) VALUES (?, ?);";
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
// 错误处理
}
// 绑定参数
sqlite3_bind_text(stmt, 1, "王五", -1, SQLITE_STATIC);
sqlite3_bind_int(stmt, 2, 20);
// 执行
rc = sqlite3_step(stmt);
if (rc != SQLITE_DONE) {
// 错误处理
}
// 重置语句以便重用
sqlite3_reset(stmt);
sqlite3_clear_bindings(stmt);
// 最终释放资源
sqlite3_finalize(stmt);
预处理语句能有效防止SQL注入,在我的安全审计中曾发现多起拼接SQL导致的漏洞案例。
c复制sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
// 执行大量INSERT/UPDATE
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
sql复制PRAGMA synchronous = OFF; -- 最快速但最不安全
PRAGMA synchronous = NORMAL; -- 折中方案
PRAGMA synchronous = FULL; -- 最安全(默认)
sql复制PRAGMA page_size = 4096;
bash复制sqlite3 test.db ".backup test.backup.db"
sql复制.mode csv
.headers on
.output data.csv
SELECT * FROM students;
.output stdout
sql复制.mode csv
.import students.csv students
sql复制.schema students
sql复制-- 将内存数据库保存到文件
ATTACH DATABASE 'backup.db' AS disk;
CREATE TABLE disk.backup AS SELECT * FROM main.mem_table;
DETACH DATABASE disk;
-- 从文件加载到内存
ATTACH DATABASE 'source.db' AS src;
CREATE TABLE main.new_table AS SELECT * FROM src.existing_table;
DETACH DATABASE src;
database is lockedc复制sqlite3_busy_timeout(db, 5000); // 5秒超时
sql复制PRAGMA foreign_keys = ON; -- 需要显式开启
sql复制ANALYZE; -- 更新统计信息
REINDEX; -- 重建索引
bash复制# 使用undark工具(需单独安装)
undark -i corrupted.db > recovery.sql
sqlite3 new.db < recovery.sql
在多年的SQLite使用经历中,我总结出最重要的经验是:虽然SQLite很强大,但它毕竟是为轻量级场景设计的。当数据量超过1GB或需要高并发写入时,建议考虑迁移到MySQL/PostgreSQL等专业数据库系统。