☰
SQLite3从入门到实战:核心语法、事务索引与Python操作全解析
2026/10/8 15:17:58 网站建设 项目流程

1. 先搞清楚SQLite3到底是什么

SQLite3这个东西,我接触了快十年,到现在依然觉得它是数据库领域里最被低估的工具之一。先说人话:SQLite3就是一个"文件型数据库",没有独立的服务器进程,没有端口,没有账号密码,整个数据库就是磁盘上的一个文件。你写程序的时候直接通过API去读写这个文件,不需要像MySQL或者PostgreSQL那样先启动一个服务,再创建用户、授权、连接远程端口。

正因为它这么轻,几乎所有你叫得上名字的软件里都有它的身影。手机上的通讯录、浏览器里的收藏夹、微信的聊天记录、很多嵌入式设备的配置存储,甚至一些大型网站的后端缓存,都在用SQLite3。如果你用Python写过东西,标准库里的sqlite3模块就是官方内置的,不需要额外装任何东西。很多做数据分析的朋友可能平时用的都是Pandas,但如果你处理的数据量超过几百万行,直接从CSV文件读就会明显变慢,这时候把数据塞进SQLite3再查,体验完全是两回事。

这篇内容我想从头到尾拆一遍SQLite3的语法,核心目标有两个:第一,让你能在半小时内搭建起自己的一套完整操作体系,从建表、增删改查到事务、索引、视图和触发器;第二,把我实际踩过的坑、用过的技巧一并交代出来。适用范围很广:还没入门的初学者可以当系统教程看,写过几年SQL但没细研究过SQLite3的工程老手,也能在这篇文章里找到一些你自己平时没注意过的细节。

SQLite3的SQL语法大体上遵循标准SQL,但又有很多独属于自己的方言。这套方言一旦掌握了,后面换到MySQL、PostgreSQL虽然不能无缝移植,但基本概念是相通的。

2. 环境准备与SQLite3安装实操

2.1 三种最省事的安装方式

先解决一个问题:怎么把SQLite3弄到本地来用。不同平台,我分别说三种最省事的方式。

第一种是命令行方式。如果你是Linux或者macOS用户,直接在终端里敲:

# Debian/Ubuntu sudo apt-get install sqlite3 # CentOS/RHEL/Fedora sudo yum install sqlite3 # macOS(自带,不用装) sqlite3 --version

macOS其实是系统自带的,Windows的话需要去SQLite官网下载预编译的二进制包。注意官网有两类文件,一类是命令行工具(sqlite-tools-win-x64),一类是动态链接库(sqlite-dll-win-x64)。如果你只是想敲SQL体验一下,下载tools就行,解压后把sqlite3.exe放到一个你记得住的目录,比如D:\software\sqlite,然后把这个目录加进Path环境变量。加好之后重新开一个终端,敲sqlite3就能进去了。

第二种方式,用Python调包。我目前干数据分析、写自动化脚本的时候基本都是这条路,因为Python自带驱动,连配置都省了,打开终端敲一行就行:

import sqlite3 conn = sqlite3.connect('demo.db') print(conn)

运行完这段,如果目录下多出一个demo.db文件,说明环境已经通了。connect()这个方法是自动创建文件的,哪怕你只是写了一个connect('test.db')然后什么都没干,文件也会被创建出来,这也是SQLite3的一个特性——零成本起步。

第三种方式是图形化工具。Navicat、DBeaver、DB Browser for SQLite这三款我都用过,其中DB Browser for SQLite是完全免费的,界面也很直观,适合完全不习惯命令行的新手。但我得说句实话:如果你真想成为SQLite3的实战高手,一定要把命令行操作练熟。图形化工具只是方便看数据,很难帮你真正建立对SQL的直觉。

2.2 进入命令行与查看基本帮助

安装完成后,终端输入sqlite3,进入交互模式,界面会显示类似sqlite>的提示符。在这个提示符下可以执行任意SQL语句,注意每条SQL语句必须以分号;结尾,这是新手最容易漏掉的一个地方。敲几条最简单的命令来验证:

-- 查看版本 sqlite> select sqlite_version(); -- 查看帮助 sqlite> .help -- 退出 sqlite> .quit

这里有个容易混淆的点:sqlite3的命令行工具内部有两种"语言"。一种是以英文句点开头的"点命令"(比如.tables、.schema、.quit、.headers on),它们不是SQL,而是命令行工具自身的功能,用于控制显示格式、列举数据库对象等;另一种才是纯粹的SQL语句(CREATE、SELECT、INSERT、UPDATE、DELETE等等)。很多新手会把.tables后面加分号,或者把SELECT写在.databases前面,然后发现莫名其妙报错。记住一句话:点命令不加分号,SQL语句必须加分号。

3. SQLite3核心语法拆解:从建表到增删改查

3.1 创建表格:类型要选对,约束要明白

几乎所有数据库操作的第一步都是建表。SQLite3的建表语法整体是这样的:

CREATE TABLE IF NOT EXISTS user ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT UNIQUE, age INTEGER DEFAULT 18, created_at TEXT DEFAULT (datetime('now', 'localtime')) );

我逐行解释一下这里面每个关键字的意义。

IF NOT EXISTS是幂等保护。如果你重复执行这段建表语句,没有这个关键字会直接报错"table user already exists",加上之后就会静默跳过。自动化脚本里建议每次都写上。

id INTEGER PRIMARY KEY AUTOINCREMENT:整型主键自增,表示每条记录的ID是唯一的,插入的时候不指定它会自动按1、2、3……排下去。这里面有一个很多人不知道的底层差异:如果只写INTEGER PRIMARY KEY(不带AUTOINCREMENT)其实也能自增,而且性能更好,区别在于删除最大ID后会不会复用之前的ID。AUTOINCREMENT会保证ID只增不减,但会多耗费一点存储空间。普通的业务表,我建议直接用INTEGER PRIMARY KEY就够了;只有当你需要绝对不重号、每个ID只用一次的场景,才需要加AUTOINCREMENT。

name TEXT NOT NULL:TEXT是SQLite3的文本类型,NOT NULL表示这一列不允许为空。比如注册用户必须填写姓名,那就在建表这一层做约束,而不是靠业务代码去判断。

age INTEGER DEFAULT 18:DEFAULT关键字,当插入数据时没有提供age值,数据库就会自动填18。这个设计很实用,可以减少很多应用层的判断代码。

created_at TEXT DEFAULT (datetime('now','localtime')):这里用的是SQLite3内置的datetime函数,取当前本地时间并格式化成YYYY-MM-DD HH:MM:SS的字符串。注意SQLite3其实没有专门的"时间类型",所有人习惯上都用TEXT存日期时间,排序和比较也都能正常工作。这一点跟MySQL差别很大,在SQLite里你永远不会看到DATETIME这个类型是独立存储的——它底层就是文本。

关于SQLite3的数据类型,官方文档里说的是"动态类型",简单理解就是:你声明某一列是INTEGER,不代表以后只能往里面放整数,实际上放字符串它也不会报错。这种宽松在开发期看似方便,但生产中非常容易埋雷。我强烈的建议是:在建表时对每一列都声明类型,业务代码里再严格校验一遍,两边都不能松。

3.2 插入数据:单条、批量与传统VALUES语法

建好表之后,最基础的操作就是插入数据。SQLite3的插入语法有三种形态,我挨个讲清楚。

第一种,指定列插入:

INSERT INTO user (name, email, age) VALUES ('张三', 'zhangsan@example.com', 25);

这种情况下,建表时设置了DEFAULT的列可以不写,让数据库自动填。比如没有指定created_at,它会自动取当前时间。

第二种,全列插入:

INSERT INTO user VALUES (1, '李四', 'lisi@example.com', 30, '2024-01-01 12:00:00');

这种写法必须把表里每一列的值都按顺序写出来,否则就会报错。从可维护性角度看,我建议少用全列插入。毕竟一旦表结构调整过(比如中间加了一列),这种INSERT语句就会全部失效,排查起来很痛苦。

第三种,批量插入,也是我实际工作中最常用的:

INSERT INTO user (name, email, age) VALUES ('王五', 'wangwu@example.com', 22), ('赵六', 'zhaoliu@example.com', 28), ('孙七', 'sunqi@example.com', 35);

这个写法在SQLite3的较新版本里是支持的,一条语句插几万条记录都没问题。如果你用Python驱动,还可以用executemany()传入一个列表,性能更高,后面的实战章节我会具体演示。

3.3 查询数据:WHERE、ORDER BY、LIMIT与聚合函数

查询是所有SQL操作里最核心也最考验功力的部分。SQLite3的基本查询语法如下:

SELECT column1, column2 FROM table_name WHERE condition ORDER BY column1 ASC/DESC LIMIT offset, count;

可以从下面几个关键点来掌握。

WHERE条件里最常用的有这些操作符:=、!=或<>、>、<、>=、<=、LIKE、IN、BETWEEN、AND、OR。举几个实际例子:

-- 精确查找 SELECT * FROM user WHERE name = '张三'; -- 模糊匹配:%表示任意长度的任意字符,_表示单个字符 SELECT * FROM user WHERE email LIKE '%@example.com'; -- 范围过滤 SELECT * FROM user WHERE age BETWEEN 20 AND 30; -- 集合过滤 SELECT * FROM user WHERE name IN ('张三', '李四', '王五');

需要提醒一个LIKE相关的细节:SQLite3的LIKE匹配在默认配置下是不区分大小写的,而且ASCII字符大小写也不敏感。如果你需要精确匹配大小写,要用GLOB关键字,它是SQLite3特有的语法,支持通配符*和?,语义上更像Unix shell的匹配规则。

ORDER BY用于排序,支持多列排序:

SELECT * FROM user ORDER BY age DESC, created_at ASC;

这个排序逻辑是:先按age降序排,age相同再按created_at升序排。设计表结构时,如果某个字段经常用于排序,给它加索引会极大提升查询速度,索引具体怎么建,我放到第4节细讲。

LIMIT有两个作用,一是限制返回行数,二是分页:

-- 返回前10条 SELECT * FROM user LIMIT 10; -- 分页:跳过前20条,取10条(第3页,每页10条) SELECT * FROM user LIMIT 10 OFFSET 20;

聚合函数方面,SQLite3提供了完整的五个基础函数:COUNT(计数)、SUM(求和)、AVG(平均值)、MAX(最大值)、MIN(最小值)。它们常常和GROUP BY配合使用:

SELECT age, COUNT(*) FROM user GROUP BY age; -- 用HAVING对分组后的结果做二次过滤 SELECT age, COUNT(*) FROM user GROUP BY age HAVING COUNT(*) > 1;

这里的HAVING和WHERE的区别是:WHERE是在分组之前过滤原始行,HAVING是在分组之后过滤分组。这个顺序搞错了,很容易写出一堆逻辑不对的查询。

3.4 更新与删除:写完条件仔细想三遍

UPDATE和DELETE在SQLite3里的语法非常简洁:

UPDATE user SET age = age + 1 WHERE name = '张三'; DELETE FROM user WHERE id = 1;

两个操作都要特别注意一件事:WHERE条件一定不能漏。如果漏了,副作用极其严重——UPDATE会把全表每一行都改掉,DELETE会把全表清空。我这句话说得很直白,因为这是我见过也经历过的翻车现场。现在我在极重要的生产库上执行DELETE之前,一定会先用同样的WHERE条件跑一遍SELECT:

-- 先查出来看看 SELECT * FROM user WHERE name = '张三'; -- 确认无误后再删 DELETE FROM user WHERE name = '张三';

在命令行工具里,DELETE操作默认是自动提交的,一旦执行没有任何恢复机制,除非你提前有备份。所以我现在凡是执行批量DELETE,都有一个习惯,先把数据导出成SQL文件:

sqlite3 demo.db ".dump" > backup.sql

这句话把整个数据库的逻辑备份导出到backup.sql文件里。万一出事,重新执行sqlite3 demo.db < backup.sql就能恢复。这个习惯值得所有动手实操的人养成。

4. SQLite3进阶语法:事务、索引、视图与触发器

4.1 事务:多条语句的"后悔药"

事务是保证数据一致性的关键机制。拿转账举例:A账户扣1000元、B账户加1000元,这两条UPDATE必须同时成功或同时失败。如果第一条成功第二条失败,钱就凭空少了。SQLite3默认是自动提交模式,也就是说每条SQL语句执行完立即生效。想要把多条语句合成一个原子操作,需要显式使用事务:

BEGIN TRANSACTION; UPDATE account SET balance = balance - 1000 WHERE id = 1; UPDATE account SET balance = balance + 1000 WHERE id = 2; COMMIT;

中间如果任何一步出了问题,执行ROLLBACK;回滚,所有操作全部撤销,数据恢复到BEGIN之前的状态。

关于SQLite3事务有几个细节点需要注意。第一,事务开启期间会锁定数据库文件,别的连接写入会被阻塞。所以事务应该短平快,不要在一个事务里做复杂的网络请求或者长时间计算。第二,SQLite3的事务还有几种隔离级别,可以通过PRAGMA journal_mode=设置,比如WAL模式(Write-Ahead Logging)在大并发下比默认的delete模式好很多,读和写可以并行。代码里只要执行一次PRAGMA journal_mode=WAL;,后续就都是WAL模式了,效果是重读不阻塞写、重写不阻塞读。我个人现在所有生产项目都会开WAL。第三,如果忘记写COMMIT就关闭了连接,未提交的事务会被自动回滚,这不一定是坏事,但如果你本意是保留数据就亏了。

4.2 索引:提速的关键但是有代价

没有索引的表相当于一本没有目录的书,查询时只能从头到尾一页页翻。SQLite3中创建索引的语法:

CREATE INDEX idx_user_email ON user(email); -- 唯一索引,确保列值不重复 CREATE UNIQUE INDEX idx_user_email_unique ON user(email);

索引为什么能提速?底层结构是B-Tree,查找时间复杂度从全表扫描的O(N)降到了索引查找的O(log N)。几万行时可能感觉不明显,到了几百万行、几千万行,差别就是毫秒和秒的区别。

索引也不是越多越好。每建一个索引,数据库在插入、更新、删除时都要额外维护索引结构,这会让写入变慢。所以基本原则是:给经常出现在WHERE和ORDER BY中的列建索引,给重复率太低的列建索引意义不大。比如性别列只有"男""女"两个值,建索引并不能大幅缩小扫描范围。

查看一个表有哪些索引,用:

-- 列出表名和索引名 SELECT * FROM sqlite_master WHERE type = 'index';

删掉索引:

DROP INDEX IF EXISTS idx_user_email;

4.3 视图:逻辑复用,简化复杂查询

视图就是一条命名的SELECT语句,它不真实存储数据,每次查询视图时底层都会去执行那条SELECT。创建视图的语法:

CREATE VIEW v_user_over_25 AS SELECT name, email, age FROM user WHERE age > 25;

创建之后,你可以像查表一样查视图:

SELECT * FROM v_user_over_25;

视图的好处是可以把复杂的多表JOIN查询封装起来,让下游使用的人只面对一张"虚拟表"。比如后端报表里经常要查用户订单汇总,就可以先建一个视图:

CREATE VIEW v_order_summary AS SELECT u.name, COUNT(o.id) AS order_count, SUM(o.amount) AS total_amount FROM user u LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id;

这样每个业务方来查报表,一行SQL就够了。视图还有一个限制要清楚:视图默认是只读的,不能直接对视图执行INSERT、UPDATE、DELETE操作(除非你创建了特定的INSTEAD OF触发器,这是更高级的玩法)。所有对视图的写操作都会报错。

4.4 触发器:当数据变化时的自动任务

触发器是SQLite3里被很多人忽略但实际很有用的功能。它可以在某个表发生INSERT、UPDATE、DELETE时自动执行一段SQL。语法结构:

CREATE TRIGGER trigger_name AFTER INSERT ON user BEGIN INSERT INTO user_log (action, user_id, operate_time) VALUES ('INSERT', NEW.id, datetime('now')); END;

这段代码的意思是:每当user表插入新行,就自动把操作记录写到user_log表。NEW代表新插入的行,OLD代表被删除或被替换的旧行。更新操作时NEW和OLD都能用,分别表示更新后的值和更新前的值。

触发器适合用来做审计日志、同步冗余字段、自动更新时间戳。不过它也有明显的坑:排错难度比普通SQL高得多。如果某天你的UPDATE发现数据异常,却怎么都查不到代码里的问题,很可能就是某个触发器在暗中作祟。查看所有触发器:

SELECT name FROM sqlite_master WHERE type = 'trigger';

删除触发器:

DROP TRIGGER IF EXISTS trigger_name;

谨慎使用,最好每个触发器都写上注释,说明用途和创建人。否则过几个月你自己都会看不懂。

5. 多表JOIN实战与命令行工具技巧

5.1 JOIN的三种主要类型

真实业务很少只操作一张表。两张表联合查询是常态。SQLite3支持主要的JOIN类型:INNER JOIN、LEFT JOIN、CROSS JOIN。简单说一个业务场景:用户表和订单表,查每个用户的订单信息。

先建两张表并插入测试数据:

CREATE TABLE user ( id INTEGER PRIMARY KEY, name TEXT NOT NULL ); CREATE TABLE orders ( id INTEGER PRIMARY KEY, user_id INTEGER, product TEXT, amount REAL ); INSERT INTO user (id, name) VALUES (1, '张三'), (2, '李四'), (3, '王五'); INSERT INTO orders (id, user_id, product, amount) VALUES (1, 1, '手机', 2999.00), (2, 1, '耳机', 199.00), (3, 2, '键盘', 459.00);

内连接只返回两边都匹配的行:

SELECT user.name, orders.product, orders.amount FROM user INNER JOIN orders ON user.id = orders.user_id;

结果:

name|product|amount 张三|手机|2999.0 张三|耳机|199.0 李四|键盘|459.0

可以看到王五没有任何订单,所以在INNER JOIN的结果里不出现。左连接(LEFT JOIN)会保留左表的全部行,右表没有匹配的部分用NULL填充:

SELECT user.name, orders.product, orders.amount FROM user LEFT JOIN orders ON user.id = orders.user_id;

结果里王五会出现一条记录,product和amount都是NULL。LEFT JOIN是日常业务里最常用的JOIN类型,特别适合"统计每个用户有多少订单"这种场景,配合GROUP BY和COUNT可以很快得出汇总。

JOIN查询里最容易犯的错误是关联条件写错导致笛卡尔积爆炸。所谓笛卡尔积,就是两张表每一行都相互组合,结果行数等于两个表行数的乘积。比如一张表1000行、另一张表1000行,不写ON条件直接JOIN会得到100万行。所以每次写JOIN,一定要检查ON后面的关联条件是否正确。

5.2 命令行工具的隐藏技巧

前面说过点命令和SQL语句的区别,这里把几个高频命令集中说一下。.headers on先打开列头显示,否则查询结果默认不显示列名,只显示一堆值。.mode column把输出变成对齐的列模式,比默认的竖线分隔好看很多。.width可以设置每列宽度。你在命令行工具里看数据不习惯,大概率就是没开这两个设置。

查看表结构的命令是.schema 表名。它会显示建表语句,非常有用。当你不确定某张表有哪些列、什么类型时,直接执行它比翻文档快得多。

从CSV文件导入数据到SQLite3,也有标准的命令。假设有一个data.csv文件,第一行是列名(id,name,age),内容用逗号分隔:

.mode csv .import data.csv user

这样就把CSV内容导入到user表。注意细节:如果表不存在,.import会自动建表,但列类型会全部变成TEXT。如果表已存在,它会按列名匹配导入。导入之前最好先看一眼文件编码,UTF-8没问题,GBK或者含BOM的可能会出问题,需要先把CSV转为UTF-8。

导出数据的话,SQLite3支持.dump和.output组合:

.output backup.sql .dump .output stdout

这样把整个数据库的建表语句和数据全部导出到backup.sql,之后可以用.read backup.sql重新载入。备份恢复这条路,我建议每个人都走一遍,不用等灾难发生再研究。

6. 从SQLite3到Python:实战代码走一遍

6.1 连接、建表、写入的基本套路

SQLite3绝大多数应用场景都不是直接在命令行敲SQL,而是写在程序里。Python内置的sqlite3模块是我用得最顺手的,先说标准流程。

import sqlite3 # 连接数据库,不存在会自动创建 conn = sqlite3.connect('shop.db') # 创建游标 cur = conn.cursor() # 建表 cur.execute(''' CREATE TABLE IF NOT EXISTS product ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, price REAL NOT NULL, stock INTEGER DEFAULT 0 ) ''') # 插入单条 cur.execute('INSERT INTO product (name, price, stock) VALUES (?, ?, ?)', ('机械键盘', 459.0, 100)) # 批量插入 data = [ ('无线鼠标', 129.0, 200), ('显示器', 1299.0, 50), ('USB-C扩展坞', 199.0, 80), ] cur.executemany('INSERT INTO product (name, price, stock) VALUES (?, ?, ?)', data) # 提交事务 conn.commit() # 查询 cur.execute('SELECT * FROM product WHERE price > ?', (200,)) rows = cur.fetchall() for row in rows: print(row) # 关闭连接 conn.close()

这里最关键的是参数占位符?。我在很多项目代码里看到有人用字符串拼接SQL:

# 这种做法极其危险 cur.execute(f"SELECT * FROM user WHERE name = '{name}'")

一旦name里包含单引号或者恶意拼接内容,轻则SQL报错,重则破坏整个数据库结构。SQLite3官方推荐的写法就是用?占位符,驱动会帮你做转义。这个习惯要在一开始就养成,哪怕只是写个脚本自己用,也建议别用字符串拼接。

另外注意Python的sqlite3模块默认不会自动提交事务。你必须显式调用conn.commit(),否则数据不会真正落盘。我早期在这个问题上栽过跟头:代码运行没报错,但是数据库文件里就是找不到数据。查了半天才发现连接关闭时事务被回滚了。

6.2 查询结果与字典模式

默认情况下,使用fetchall()拿到的是一个由元组组成的列表,每一行是一个元组,只能靠下标访问列,比如row[0]是id,row[1]是name。当表结构比较简单时问题不大,一旦列数变多或者你经常改动表结构,这种写法就变得非常不友好。

SQLite3的Python模块提供了Row类型和字典模式。通过设置连接的行工厂,可以让每行像一个字典一样通过列名访问:

conn = sqlite3.connect('shop.db') conn.row_factory = sqlite3.Row cur = conn.cursor() cur.execute('SELECT id, name, price FROM product WHERE stock > ?', (0,)) rows = cur.fetchall() for row in rows: print(row['name'], row['price'])

这样代码的可读性能上一个台阶。尤其是在写复杂的查询逻辑时,row['name']比row[1]直观太多了,列顺序发生变化也不会导致代码默默取错数据。

对于只读场景,还可以把连接设置为自动提交,省去手动commit:

conn = sqlite3.connect('file:shop.db?mode=ro', uri=True)

file:这种URI写法是SQLite3支持的高级特性,mode=ro表示以只读模式打开。如果你的脚本只需要查询不需要写入,推荐用这个模式,可以有效防止手滑误操作。

6.3 事务在Python中的正确姿势

在Python里操作事务,不能用裸的BEGIN语句。因为Python的sqlite3模块在conn.commit()和conn.rollback()之外,还默认隐藏了一些事务行为。比较规范的做法是用with上下文管理器:

conn = sqlite3.connect('shop.db') try: with conn: cur = conn.cursor() cur.execute('UPDATE product SET stock = stock - ? WHERE id = ?', (1, 101)) cur.execute('UPDATE product SET stock = stock - ? WHERE id = ?', (2, 1)) except sqlite3.Error as e: print('事务执行失败,已自动回滚', e)

with conn块结束时,如果内部没有抛出异常就自动执行commit;如果抛出了异常则自动执行rollback。这是我目前最推荐的事务写法,比手动BEGIN加COMMIT少操心很多。

在使用过程中我还发现,连接对象和游标对象的生命周期要清晰。连接负责事务、提交、回滚,游标负责执行SQL和获取结果。每次操作都新建游标没问题,但连接不要频繁打开和关闭,尤其不要在一个循环里反复connect,每次都重新打开文件,非常浪费性能。正确做法是连接一次,在整个程序的入口处创建,结束时统一关闭。

7. 常见问题排查与实用技巧

7.1 SQLITE_BUSY:并发写入冲突

SQLite3最常见的报错之一是database is locked,底层错误码是SQLITE_BUSY。这意味着另一个连接正在写数据库,当前连接想写的尝试被拒绝了。SQLite3默认对并发写入的支持比较弱,因为它每次写入都会锁住整个数据库文件。

排除这个问题有几个思路。优先检查是否有别的进程打开了同一个库文件忘记关闭。然后尝试开启WAL模式,它显著改善了读写并发:

PRAGMA journal_mode=WAL;

在Python驱动中,还可以设置连接等待超时:

conn = sqlite3.connect('shop.db', timeout=10)

这个timeout表示当数据库被锁时最多等待10秒,超过后再抛出超时错误。这个参数默认是5秒,按需调整即可。

7.2 使用WAL模式后的附加文件

开启WAL模式之后,数据库目录下会发现多出两个文件:shop.db-wal和shop.db-shm。很多人会以为它们是垃圾文件把它删掉,这是大忌。WAL模式会把尚未合并到主数据库的写操作临时放在-wal文件里,删除它可能导致数据丢失。它们会在连接关闭、checkpoint正常执行后自动清理。备份数据库时也要注意:直接复制.db文件不够,需要连-wal一起复制,或者先执行一次PRAGMA wal_checkpoint;把数据合并到主文件,再复制主文件。

7.3 数据备份的简易脚本

备份SQLite3最稳妥、跨平台的方式是用.dump导出逻辑备份。在Python里可以用以下方式:

import sqlite3 def backup_sqlite(db_path, backup_path): conn = sqlite3.connect(db_path) with open(backup_path, 'w', encoding='utf-8') as f: for line in conn.iterdump(): f.write('%s\n' % line) conn.close() backup_sqlite('shop.db', 'shop_backup.sql')

iterdump()会生成重建整个数据库所需的SQL语句,包括表结构、索引、视图、触发器和数据。这个备份文件可以完整复原数据库。恢复时只需要在空的数据库中执行里面的SQL即可:

import sqlite3 conn = sqlite3.connect('new_shop.db') with open('shop_backup.sql', 'r', encoding='utf-8') as f: sql_script = f.read() conn.executescript(sql_script) conn.commit() conn.close()

7.4 性能优化的几个小方向

数据库操作卡顿是常见问题,性能优化有几个优先级非常高的方向。

第一,使用索引覆盖查询。如果你查的表很大,且查询条件经常落在某一个字段上,就给这个字段加索引。索引带来的速度提升通常是指数级的。

第二,批量提交。在Python里用executemany批量写入比逐条execute快一个量级。如果数据量很大,还可以配合每5000条一次commit的频率,找到性能和事务原子性的平衡点。

第三,避免在循环中执行无关查询。例如循环里对每个用户ID查数据库,那就要想一想能不能用一条IN查询代替:

# 不要循环查 for uid in user_ids: cur.execute('SELECT * FROM user WHERE id=?', (uid,)) # 改用一条查询 placeholders = ','.join(['?'] * len(user_ids)) cur.execute(f'SELECT * FROM user WHERE id IN ({placeholders})', user_ids)

第四,如果只有插入和查询需求,可以临时关闭索引或开启PRAGMA synchronous = OFF来换写入速度。注意这只是在批量导入数据时临时用,生产环境还是保持默认值更安全。

7.5 学习路径建议

从零基础到实战高手,不用一口气把整份文档背完。我建议的学习路线是先掌握第3章的所有基础操作,每天用真实业务场景练一练,比如记录支出流水、管理书籍库存之类的小应用。等基础熟了,再上第4章的索引和事务。等到你写的查询开始变慢,就会发现索引的必要性;等你的程序开始并发读写,就会理解事务的意义。最后再研究触发器、视图这些锦上添花的功能。

自己在本地折腾时完全不用怕弄坏数据库,建一个test.db,随便造,大不了删掉重建。实践多少次都不为过。遇到报错就多看错误信息,SQLite3本身是个很成熟很稳定的系统,绝大多数异常都能靠仔细阅读提示找到破绽。

我在实际项目中用SQLite3写了大量工具,包括数据采集清洗、报表生成、文件索引管理,甚至还有一个小团队的内部CTF题管理平台。每次遇到SQLite3的问题去查文档,都能发现一些之前没注意到的用法。这也是它最有魅力的地方:看似简单,其实藏着无穷多值得深挖的细节。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询