Python 接口入门(sqlite3)
本教程共 50 篇 · 第 47 篇 · 更新于 2026-07-31
47. Python 接口入门(sqlite3)
本节目标:学完本章你能用 Python 自带的
sqlite3标准库,完成”连接数据库 → 建表 → 插入 → 查询 → 更新 → 删除”的最小闭环,并理解事务与参数化在 Python 中的写法。
SQLite 最大的魅力之一,是它不需要你启动任何数据库服务。它是 serverless(无服务进程)、零配置的:数据库就是一个磁盘文件,你的程序通过库函数直接读写它。在 Python 世界里,这份便利更进一步——从 Python 2.5 起,标准库就内置了 sqlite3 模块,你不用额外安装任何东西就能直接 import sqlite3。这也呼应了第 46 章的要点:既然没有服务器帮你兜底,连接、安全和事务都得自己代码负责。
本章只演示 sqlite3 标准库的最小读写,不引入任何 Web 框架、不使用 SQLAlchemy 等 ORM——先把手感练对,将来接框架才不会被层层封装绕晕。
一、连上数据库
sqlite3.connect() 接受一个文件名;如果文件不存在,它会自动创建。你也可以用 ":memory:" 在内存里建一个临时库(程序退出就没了)。因为我们反复强调”数据库即文件”,所以这里用真实的文件更直观:
import sqlite3
conn = sqlite3.connect("demo.db") # 文件不存在就新建,存在就打开
print("连接成功")
连接对象 conn 代表一次数据库会话。多数操作要先拿到一个”游标(cursor)“,再用它执行 SQL:
c = conn.cursor()
二、建表(用全局统一示例表)
我们用教程贯穿的 users 表:
c.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT,
age INTEGER,
email TEXT
)
""")
conn.commit() # DDL 也建议 commit,确保落盘
这里 id INTEGER PRIMARY KEY 又一次体现 SQLite 的特色:它隐式自增,是底层 rowid 的别名,你插入时不写 id,SQLite 会自动填上比当前最大值大 1 的值。不需要写 AUTOINCREMENT 关键字——那是可选的、仅在”永不重用已删除的 rowid”时才考虑,还会带来 sqlite_sequence 表与额外开销。日常自增主键,写 INTEGER PRIMARY KEY 就够了。
三、插入:参数化是铁律
还记得第 46 章吗?插入值必须走参数,绝不拼字符串:
c.execute(
"INSERT INTO users (name, age, email) VALUES (?, ?, ?)",
("小明", 19, "ming@example.com")
)
conn.commit()
多个值用 executemany 一次插入:
rows = [
("小红", 20, "hong@example.com"),
("小刚", 22, "gang@example.com"),
]
c.executemany("INSERT INTO users (name, age, email) VALUES (?, ?, ?)", rows)
conn.commit()
实用技巧
如果你嫌按位置对
?容易数错,可以用命名占位符:name配字典:c.execute( "INSERT INTO users (name, age, email) VALUES (:n, :a, :e)", {"n": "小美", "a": 21, "e": "mei@example.com"} ) conn.commit()
四、查询:读取结果
execute 之后用 fetchall() / fetchone() / fetchmany(n) 取结果,每行是一个元组:
for row in c.execute("SELECT id, name, age FROM users ORDER BY age"):
print(row)
# (19, '小明', 19) (21, '小美', 21) ...
想要”按列名取”而不是”按下标取”,把 row_factory 设成 sqlite3.Row:
conn.row_factory = sqlite3.Row
for row in conn.execute("SELECT * FROM users"):
print(row["name"], row["email"]) # 像字典一样按列名访问
带条件查询同样参数化:
c.execute("SELECT * FROM users WHERE age > ?", (20,))
print(c.fetchall())
如果结果很多、不想一次性全读进内存,用 fetchmany(n) 分批取:
c.execute("SELECT * FROM users ORDER BY id")
while True:
batch = c.fetchmany(100) # 每次取 100 行
if not batch:
break
for row in batch:
print(row)
四之二、把 users 和 orders 一起用(外键要点)
教程贯穿还有一张 orders 表,它用 user_id 指向 users.id:
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
user_id INTEGER,
amount REAL,
created_at TEXT
);
如果你希望”删掉一个用户时,数据库拦住尚有订单的用户”,就要开外键约束——但切记 SQLite 默认不强制外键,必须显式开启,且每次新连接都要开:
conn = sqlite3.connect("demo.db")
conn.execute("PRAGMA foreign_keys = ON") # 关键!默认 OFF
c = conn.cursor()
c.execute("""CREATE TABLE IF NOT EXISTS orders (
id INTEGER PRIMARY KEY, user_id INTEGER,
amount REAL, created_at TEXT,
FOREIGN KEY(user_id) REFERENCES users(id)
)""")
conn.commit()
漏掉 PRAGMA foreign_keys = ON,参照完整性就形同虚设,这是新手最常踩的坑之一。
四之三、Python 值与存储类的对应关系
SQLite 是动态类型 + 类型亲和性(Type Affinity):列声明只是”亲和性建议”,实际存什么由值决定。sqlite3 在写入时会按下面的规则把 Python 值映射成 5 种**存储类(Storage Class)**之一——NULL / INTEGER / REAL / TEXT / BLOB:
- Python
int→ INTEGER(注意:超过 2^63 的大整数会被转成 REAL,可能丢精度) - Python
float→ REAL - Python
str→ TEXT - Python
bytes→ BLOB - Python
None→ NULL
正因如此,没有 DATE/TIME 这类日期类型:日期请用 ISO8601 字符串(TEXT)、unix 秒(INTEGER)或儒略日(REAL)存,读取后自己解析。理解这层映射,你就不会被”为什么 TEXT 列里能存进数字”这类动态类型现象困惑。
五、更新与删除
c.execute("UPDATE users SET age = ? WHERE name = ?", (23, "小刚"))
conn.commit()
c.execute("DELETE FROM users WHERE name = ?", ("小美",))
conn.commit()
常见坑
第一,
UPDATE/DELETE一定要带WHERE,否则会改/删整张表。第二,conn.close()不会自动commit()——如果忘了commit()就直接close(),你的改动会全部丢失。第三,涉及orders等带外键的表时,SQLite 默认不强制外键,需先执行PRAGMA foreign_keys = ON(每次连接都要开),否则参照完整性形同虚设。
六、事务:用 with 更省心
sqlite3 的连接对象本身可以当”上下文管理器”用。with conn: 会把块内操作包成一个事务,正常结束自动 commit(),抛异常自动 rollback(),再也不怕漏写 commit:
with conn:
conn.execute("INSERT INTO users (name, age, email) VALUES (?, ?, ?)",
("阿强", 25, "qiang@example.com"))
# 离开 with 块即自动提交
六之二、异常处理:出错要回滚
真实程序里写入可能失败(比如违反了 NOT NULL、UNIQUE 约束)。用 try/except 包住写操作,出错时 rollback() 把没提交的部分撤掉,避免半截数据残留在库里:
try:
with conn:
conn.execute(
"INSERT INTO users (name, age, email) VALUES (?, ?, ?)",
("小雷", 28, "lei@example.com")
)
except sqlite3.Error as e:
print("写入失败,已回滚:", e)
因为 with conn: 在异常时会自动 rollback(),这里即使不手动调也安全;手动 try/except 主要是为了”捕获错误、给出友好提示”,而不是让程序崩掉。
另外提醒:INTEGER PRIMARY KEY 之所以能隐式自增,是因为它直接映射到底层 rowid——你插入时不给 id,SQLite 自动填”当前最大 rowid + 1”。如果想用文本做主键(比如 UUID),就写 id TEXT PRIMARY KEY,此时它不会自增,需要你自己保证唯一。
六之三、顺手的小工具方法
sqlite3 还提供几个省心的小帮手。想看表里有哪些列,不必切回 CLI,用 PRAGMA table_info 即可:
for col in conn.execute("PRAGMA table_info(users)"):
print(col[1], col[2]) # 列名、声明类型
想知道”本次连接以来总共改了多少行”,用 conn.total_changes:
conn.execute("UPDATE users SET age = age + 1")
print("累计影响行数:", conn.total_changes)
这些在调试或写小工具时很顺手,但记住它们只是语法糖,底层仍然是你已经学过的 SQL 与事务逻辑。
七、收尾与类比小结
用完记得关连接,释放文件锁:
conn.close()
把 sqlite3 模块类比成一个”文件读写助手”:你 connect 打开文件,execute 往里写指令,commit 真正落盘,close 合上文件。它和文本文件不同的是——底层是 B 树组织的页式存储(第 49 章会讲),所以即使是几百万行的查询也很快。你今天写的每一行 Python,最终都被这个助手翻译成对那个 .db 文件的精准读写,没有中间商、没有网络往返,这正是嵌入式数据库”又快又简单”的根本原因。
重点提示
sqlite3是 Python 官方标准库,API 遵循 DB-API 2.0 规范,所以你今天学的connect / cursor / execute / commit / rollback套路,迁移到 MySQL(用pymysql)、PostgreSQL(用psycopg)时也高度相似,只是换了个驱动模块。但参数化?占位和”显式 commit”这两条习惯,在哪里都成立。
最后提醒:本教程定位是”纯知识点干货,不含实战项目”,所以本章只演示标准库最小读写。等你在真实项目里用 Flask/Django/FastAPI 时,它们底层调用的依然是这一套 sqlite3 能力,理解原理比记框架 API 更重要。