Python 关系型数据库操作完全指南
概述
Python 通过统一的 DB-API 2.0 接口操作各类关系型数据库。本专题聚焦两大主流方案:
| 方案 | 架构 | 适用场景 |
|---|---|---|
| 第一部分 · SQLite | 嵌入式,单文件 | 本地存储、桌面/移动应用、测试与原型、低并发 Web |
| 第二部分 · MySQL | 客户端-服务器 | 高并发 Web、生产级业务系统、海量数据 |
二者都遵循 PEP 249 — DB-API 2.0 标准,均使用参数化查询防止 SQL 注入,都支持事务管理。
第一部分:SQLite(嵌入式)
title: Python 操作 SQLite 完全指南 description: 深入掌握 Python 内置 sqlite3 模块,涵盖连接管理、游标操作、参数化查询、事务控制、行工厂、上下文管理器与生产级最佳实践。 version: 1.0 author: 文档维护组 created: 2026-06-05 updated: 2026-06-05 status: 正式
概述
SQLite 是一种 嵌入式关系型数据库引擎,它的数据库就是一个独立的 .db 磁盘文件。与 MySQL、PostgreSQL 等服务端数据库不同,SQLite 不需要独立的服务器进程——应用程序直接读写数据库文件,所有操作都在调用进程内完成。
适用场景:
| 场景 | 说明 |
|---|---|
| 本地存储 | 桌面应用、移动 App、浏览器(Chrome/Safari 内部大量使用 SQLite) |
| 嵌入式设备 | IoT 设备、路由器配置文件存储 |
| 测试与原型 | 无需搭建数据库服务器,快速验证 SQL 逻辑 |
| 轻量 Web 应用 | 单用户或低并发场景(如个人博客、小型工具站) |
| 数据分析中间存储 | 将中间计算结果持久化,避免重复计算 |
不适用场景:
| 场景 | 替代方案 |
|---|---|
| 高并发写入 | MySQL、PostgreSQL |
| 海量数据(TB 级) | PostgreSQL、分布式数据库 |
| 需要网络访问 | 几乎所有服务端数据库 |
DB-API 2.0 规范
Python 定义了 PEP 249 — Python Database API Specification v2.0,所有 Python 数据库驱动(sqlite3、mysql-connector-python、PyMySQL、psycopg2 等)都遵循相同的接口约定:
核心对象模型:
| 对象 | 职责 | 获取方式 |
|---|---|---|
Connection | 管理数据库连接、事务控制 | sqlite3.connect(db_path) |
Cursor | 执行 SQL 语句、遍历结果集 | conn.cursor() |
Row | 结果集的单行表示 | cursor.fetchone() / fetchall() |
标准操作流程:
连接管理
基础连接
import sqlite3
# 若文件不存在,自动创建
conn = sqlite3.connect('data.db')connect() 的完整签名:
sqlite3.connect(
database, # 数据库文件路径,或 ':memory:' 创建内存数据库
timeout=5.0, # 数据库被锁时等待的时间(秒)
detect_types=0, # 是否自动检测列类型
isolation_level='', # 事务隔离级别(见下文)
check_same_thread=True, # 是否检查线程安全
uri=False, # 是否将 database 参数解释为 URI
)使用上下文管理器
Python 3.x 的 sqlite3.Connection 支持 with 语句,退出时自动提交或回滚:
import sqlite3
# 推荐写法:自动管理事务
with sqlite3.connect('data.db') as conn:
cursor = conn.cursor()
cursor.execute('CREATE TABLE IF NOT EXISTS users(id INTEGER PRIMARY KEY, name TEXT)')
cursor.execute('INSERT INTO users(name) VALUES(?)', ('Alice',))
# with 块结束时自动 commit;若抛异常则自动 rollback注意:
with conn只管理事务(提交/回滚),不会自动关闭连接。连接在conn.close()或离开 with 块后仍存活,直到被 GC 回收或显式关闭。生产环境中建议手动conn.close()。
使用 with 管理 Cursor
with sqlite3.connect('data.db') as conn:
with conn:
# 双层 with:
# conn.__exit__ → commit/rollback
# conn.cursor().__exit__ → close cursor
conn.execute('CREATE TABLE IF NOT EXISTS log(ts TEXT, msg TEXT)')
conn.execute('INSERT INTO log VALUES(datetime("now"), ?)', ('service started',))快捷方式:
Connection.execute()是cursor().execute()的简写,会自动创建并关闭游标。
核心操作
CRUD 全流程
####### 建表与插入
import sqlite3
with sqlite3.connect('school.db') as conn:
conn.execute('''
CREATE TABLE IF NOT EXISTS student (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER CHECK(age > 0 AND age < 150),
score REAL DEFAULT 0.0
)
''')
# 单行插入
conn.execute(
'INSERT INTO student(name, age, score) VALUES(?, ?, ?)',
('张三', 20, 88.5)
)
# 多行批量插入
students = [
('李四', 22, 91.0),
('王五', 19, 76.5),
('赵六', 21, 85.0),
]
conn.executemany('INSERT INTO student(name, age, score) VALUES(?, ?, ?)', students)
print(f'插入了 {conn.execute("SELECT COUNT(*) FROM student").fetchone()[0]} 条记录')####### 查询
import sqlite3
with sqlite3.connect('school.db') as conn:
# 在连接上启用 Row 工厂,使结果可以通过列名访问
conn.row_factory = sqlite3.Row
# 条件查询
rows = conn.execute(
'SELECT id, name, age, score FROM student WHERE score >= ? ORDER BY score DESC',
(80,)
).fetchall()
for row in rows:
# 使用列名访问,比按索引更可读
print(f'{row["id"]:>3} | {row["name"]:<6} | {row["age"]:>3} | {row["score"]:>5}')row_factory 的常见配置:
| 工厂 | 效果 | 示例 |
|---|---|---|
默认 tuple | 按位置索引 | row[0] |
sqlite3.Row | 按列名或索引访问 | row['name'] 或 row[0] |
自定义 dict | lambda c, r: dict(zip([col[0] for col in c.description], r)) | row['name'] |
namedtuple | 按属性访问 | row.name |
####### 更新与删除
with sqlite3.connect('school.db') as conn:
# 更新
affected = conn.execute(
'UPDATE student SET score = score + 5 WHERE score < 80'
).rowcount
print(f'更新了 {affected} 行')
# 删除
affected = conn.execute(
'DELETE FROM student WHERE id = ?', (3,)
).rowcount
print(f'删除了 {affected} 行')结果集获取方法
| 方法 | 返回值 | 适用场景 |
|---|---|---|
fetchone() | 单行或 None | 按需逐行读取,内存友好 |
fetchmany(size) | 最多 size 行列表 | 分批处理大数据集 |
fetchall() | 所有行列表 | 小数据集一次性加载 |
| 迭代 Cursor | 生成器逐行产出 | 推荐 — 内存最优 |
# 推荐:迭代 Cursor 逐行处理,不一次性加载到内存
with sqlite3.connect('large.db') as conn:
for row in conn.execute('SELECT * FROM huge_table'):
process(row)SQL 注入与参数化查询
问题:字符串拼接的危险
# ❌ 危险:字符串拼接,攻击者输入 ' OR 1=1 --' 即可绕过认证
user_input = "admin' OR '1'='1"
sql = f"SELECT * FROM users WHERE name='{user_input}'"
# 实际执行的 SQL:
# SELECT * FROM users WHERE name='admin' OR '1'='1'
# 返回所有用户!# ✅ 安全:参数化查询,数据库驱动自动转义
cursor.execute('SELECT * FROM users WHERE name=?', (user_input,))SQLite 参数占位符
| 风格 | 占位符 | 示例 |
|---|---|---|
| 问号风格 (qmark) | ? | execute('... WHERE a=? AND b=?', (1, 2)) |
| 命名风格 (named) | :name | execute('... WHERE a=:a AND b=:b', {'a':1,'b':2}) |
| 数字风格 (numeric) | :1 | execute('... WHERE a=:1 AND b=:2', (1, 2)) |
SQLite 原生支持 qmark、named、numeric 三种风格;MySQL 使用
%s;PostgreSQL 使用%s或$1, $2。参数化查询的关键原则是:绝不用字符串拼接构造 SQL。
事务管理
SQLite 的隐式事务
SQLite 默认开启隐式事务:任何 INSERT/UPDATE/DELETE 语句都会自动启动一个事务,直到显式 commit() 或 rollback()。
显式事务控制
import sqlite3
with sqlite3.connect('bank.db') as conn:
# 转账操作:必须同时成功或同时失败
# 方案一:双层 with 自动管理
with conn:
conn.execute('UPDATE accounts SET balance = balance - 100 WHERE id = ?', (1,))
conn.execute('UPDATE accounts SET balance = balance + 100 WHERE id = ?', (2,))
# 离开 with conn 块:自动 commit
# 若任何一步抛异常:自动 rollback
# 方案二:手动控制
try:
conn.execute('UPDATE accounts SET balance = balance - 100 WHERE id = ?', (1,))
# 模拟中间出错
# raise ValueError('模拟失败')
conn.execute('UPDATE accounts SET balance = balance + 100 WHERE id = ?', (2,))
conn.commit()
print('转账成功')
except Exception as e:
conn.rollback()
print(f'转账失败,已回滚: {e}')隔离级别
| 参数值 | 含义 |
|---|---|
isolation_level=None | 自动提交模式(每条语句自动 commit) |
isolation_level='' | 标准模式(隐式事务 + 显式 commit) |
isolation_level='DEFERRED' | 延迟事务(SQLite 默认,加锁延迟到实际写入时) |
isolation_level='IMMEDIATE' | 立即事务(连接时立即获取写锁) |
isolation_level='EXCLUSIVE' | 排他事务(连接时独占整个数据库) |
# 自动提交模式:每条语句立即生效
conn = sqlite3.connect('data.db', isolation_level=None)
# WAL 模式 + 立即事务:高并发场景推荐
conn = sqlite3.connect('data.db', isolation_level='IMMEDIATE')
conn.execute('PRAGMA journal_mode=WAL') # 启用 Write-Ahead Logging高级特性
回滚数据保留
import sqlite3
with sqlite3.connect('analytics.db') as conn:
# 开启 rollback 日志(可选)
conn.execute('PRAGMA journal_mode=WAL')
# 通过隔离 savepoint 保留部分回滚数据
conn.execute("SAVEPOINT before_batch")
try:
conn.execute("INSERT INTO events VALUES(?, ?)", ("page_view", 100))
conn.execute("INSERT INTO events VALUES(?, ?)", ("click", 50))
# ...
conn.execute("RELEASE before_batch") # 确认保存点
except Exception:
conn.execute("ROLLBACK TO before_batch") # 只回滚到保存点
conn.execute("INSERT INTO error_log VALUES(datetime('now'), 'batch_failed')")自定义函数注册
SQLite 允许将 Python 函数注册为 SQL 函数:
import sqlite3
import hashlib
def md5(text):
return hashlib.md5(text.encode()).hexdigest()
with sqlite3.connect(':memory:') as conn:
conn.create_function('md5', 1, md5)
result = conn.execute("SELECT md5('hello')").fetchone()[0]
print(result) # 5d41402abc4b2a76b9719d911017c592聚合函数
class Median:
"""自定义聚合函数:计算中位数"""
def __init__(self):
self.values = []
def step(self, value):
if value is not None:
self.values.append(value)
def finalize(self):
if not self.values:
return None
self.values.sort()
n = len(self.values)
mid = n // 2
return self.values[mid] if n % 2 else (self.values[mid-1] + self.values[mid]) / 2
with sqlite3.connect(':memory:') as conn:
conn.create_aggregate('median', 1, Median)
conn.execute('CREATE TABLE scores(score REAL)')
conn.executemany('INSERT INTO scores VALUES(?)', [(85,), (90,), (78,), (92,), (88,)])
median = conn.execute('SELECT median(score) FROM scores').fetchone()[0]
print(f'中位数: {median}') # 88.0备份与恢复
import sqlite3
def backup_database(source_path, dest_path):
"""在线备份 SQLite 数据库"""
source = sqlite3.connect(source_path)
dest = sqlite3.connect(dest_path)
with dest:
source.backup(dest)
source.close()
dest.close()
backup_database('production.db', 'production_backup.db')性能优化要点
| 优化项 | 操作 | 收益 |
|---|---|---|
| WAL 模式 | PRAGMA journal_mode=WAL | 读写并发不阻塞 |
| 批量插入 | executemany() + 单事务 | 10x-100x 写入提速 |
| 同步模式 | PRAGMA synchronous=NORMAL | 写入提速 2x(少量安全保障降低) |
| 内存缓存 | PRAGMA cache_size=-8000 | 8MB 缓存(负值表示 KB) |
| 临时存储 | PRAGMA temp_store=MEMORY | 临时表/排序在内存中进行 |
| 索引 | 为高频查询列建索引 | 查询提速 10x-1000x |
批量插入最佳实践:
import sqlite3
# ❌ 慢:逐条插入,每条一个事务
# 1万条数据需要约 60 秒
# ✅ 快:批量 + 单事务
with sqlite3.connect('data.db') as conn:
conn.execute('PRAGMA synchronous=NORMAL')
conn.execute('PRAGMA journal_mode=WAL')
conn.execute('CREATE TABLE IF NOT EXISTS records(id INTEGER, data TEXT)')
batch_size = 1000
data = [(i, f'data-{i}') for i in range(10000)]
with conn: # 整个批量在一个事务中
conn.executemany('INSERT INTO records VALUES(?, ?)', data)
# 1万条数据约 0.5 秒 — 120x 提速常见陷阱
| 陷阱 | 说明 | 正确做法 |
|---|---|---|
| 忘记 commit | 数据不保存,程序退出后丢失 | 使用 with conn: 自动提交 |
| 不关连接 | 连接泄漏,文件锁不释放 | 使用 with connect(...) as conn: |
| 字符串拼接 SQL | SQL 注入风险 | 始终使用 ? 参数化查询 |
| 多线程共享连接 | SQLite 默认 check_same_thread=True | 每个线程独立连接或使用连接池 |
| 不处理 busy 超时 | 高并发时频繁 database is locked | 设置 timeout 参数 + WAL 模式 |
| 不建索引 | 全表扫描,大数据量极慢 | 为 WHERE / JOIN / ORDER BY 列建索引 |
| 把 SQLite 当 MySQL 用 | 并发写入性能差 | 高并发写入场景用服务端数据库 |
术语表
| 术语 | 定义 |
|---|---|
| DB-API 2.0 | Python 数据库接口规范,定义 Connection/Cursor/execute 等标准 API |
| Connection | 数据库连接对象,管理事务与连接生命周期 |
| Cursor | 游标对象,执行 SQL 并遍历结果集 |
| Parameterized Query | 参数化查询,用占位符代替字符串拼接,防止 SQL 注入 |
| WAL | Write-Ahead Logging,先写日志再写数据文件,提升并发读写性能 |
| row_factory | 行工厂函数,控制查询返回行的数据类型 |
| savepoint | 事务内部的子保存点,支持部分回滚 |
延伸阅读
- SQLite 官方文档 — SQL 语法、数据类型、特性参考
- Python sqlite3 模块文档 — 官方 API 参考
- PEP 249 — DB-API 2.0 — Python 数据库接口规范
- SQLite WAL 模式详解 — 并发读写原理
第二部分:MySQL(服务端)
title: Python 操作 MySQL 完全指南 description: 深入掌握 Python 操作 MySQL,涵盖 mysql-connector-python、PyMySQL、SQLAlchemy ORM、连接池、SQL 注入防护、事务管理与生产级最佳实践。 version: 1.0 author: 文档维护组 created: 2026-06-05 updated: 2026-06-05 status: 正式
概述
MySQL 是业界最流行的开源关系型数据库之一,采用 客户端-服务器架构。与 SQLite 不同,MySQL 服务器以独立进程运行,通过网络对外提供服务,Python 程序通过数据库驱动(Driver)与之通信。
Python MySQL 驱动生态
| 驱动 | 特点 | 适用场景 |
|---|---|---|
| mysql-connector-python | MySQL 官方纯 Python 实现 | 快速原型、无 C 依赖环境 |
| PyMySQL | 纯 Python 实现,API 简洁 | 通用场景、性能测试造数据 |
| mysqlclient | C 扩展,性能最优 | 高并发生产环境 |
| aiomysql | 基于 PyMySQL 的异步驱动 | asyncio 异步项目 |
mysql-connector-python
MySQL 官方提供的纯 Python 驱动,安装简单:
pip install mysql-connector-python基本 CRUD 操作
####### 建表与插入
import mysql.connector
conn = mysql.connector.connect(
user='root',
password='root1234',
database='test'
)
cursor = conn.cursor()
# 创建表
cursor.execute('''
CREATE TABLE user (
id VARCHAR(20) PRIMARY KEY,
name VARCHAR(20) NOT NULL
)
''')
# 插入单行 — MySQL 占位符为 %s
cursor.execute(
'INSERT INTO user (id, name) VALUES (%s, %s)',
['1', 'Michael']
)
print(f'影响行数: {cursor.rowcount}')
conn.commit()
cursor.close()
conn.close()####### 查询
import mysql.connector
conn = mysql.connector.connect(
user='root', password='root1234', database='test'
)
cursor = conn.cursor()
cursor.execute('SELECT * FROM user WHERE id = %s', ('1',))
values = cursor.fetchall()
print(values) # [('1', 'Michael')]
cursor.close()
conn.close()PyMySQL
PyMySQL 是社区最广泛使用的纯 Python MySQL 驱动,API 与 mysql-connector-python 高度兼容。
pip install pymysqlCRUD 完整示例
####### 插入数据
import pymysql
conn = pymysql.connect(
host="localhost",
port=3306,
user="root",
password="root1234",
database="vega"
)
cursor = conn.cursor()
sql = "INSERT INTO t_user(username, password, email, role_id) VALUES(%s, %s, %s, %s)"
# 单行插入
cursor.execute(sql, ("user1", "pwd_hash", "user1@qq.com", 1))
# 批量插入 — 性能远优于逐条 execute
data = [
("user2", "pwd_hash_2", "user2@qq.com", 1),
("user3", "pwd_hash_3", "user3@qq.com", 1),
]
cursor.executemany(sql, data)
conn.commit()
cursor.close()
conn.close()####### 更新数据
import pymysql
conn = pymysql.connect(
host="localhost", port=3306,
user="root", password="root1234", database="vega"
)
cursor = conn.cursor()
sql = 'UPDATE t_user SET username = %s WHERE id = %s'
# 单行更新
cursor.execute(sql, ('new_name', 23))
# 批量更新(一次性提交多组参数)
cursor.executemany(sql, [('name_22', 22), ('name_21', 21)])
conn.commit()
cursor.close()
conn.close()####### 删除数据
sql = 'DELETE FROM t_user WHERE id = %s'
# 单行删除
cursor.execute(sql, (3,))
# 批量删除
cursor.executemany(sql, [(4,), (5,)])
conn.commit()####### 查询数据 — 三种获取方式
cursor.execute("SELECT * FROM t_user")
# 方式一:逐行获取
row = cursor.fetchone()
print(row)
# 方式二:分批获取(适合大数据集,控制内存)
rows = cursor.fetchmany(5)
for row in rows:
print(row)
# 方式三:全部获取(适合小数据集)
results = cursor.fetchall()
for row in results:
print(row)注意:
fetchone()/fetchmany()/fetchall()共享同一个结果集游标。先调用fetchone()再调用fetchall()时,fetchall()只返回剩余未读取的行。
SQL 注入与参数化查询
攻击原理
攻击演示
import pymysql
conn = pymysql.connect(
host="localhost", port=3306,
user="root", password="root1234", database="vega"
)
cursor = conn.cursor()
username = "1 OR 1=1"
password = "1 OR 1=1"
# ❌ 危险:字符串拼接
sql_bad = (
"SELECT COUNT(*) FROM t_user WHERE username=" + username +
" AND AES_DECRYPT(UNHEX(password),'HelloWorld')=" + password
)
cursor.execute(sql_bad)
print(f'SQL注入成功,返回: {cursor.fetchone()[0]}') # 绕过认证
# ✅ 安全:参数化查询
sql_safe = (
"SELECT COUNT(*) FROM t_user WHERE username=%s "
"AND AES_DECRYPT(UNHEX(password),'HelloWorld')=%s"
)
cursor.execute(sql_safe, (username, password))
print(f'参数化查询,返回: {cursor.fetchone()[0]}') # 0 — 注入失败
cursor.close()
conn.close()参数化查询原则
| 原则 | 说明 |
|---|---|
| 永远不拼接 SQL | 任何用户输入都不应直接拼入 SQL 字符串 |
| 使用占位符 | MySQL 驱动使用 %s,SQLite 使用 ? |
| 参数独立传递 | 将参数作为 execute() 的第二个参数传入 |
| 动态表名/列名 | 使用白名单校验,不能用占位符(占位符仅适用于值) |
# ✅ 动态列名:白名单校验
ALLOWED_COLUMNS = {'id', 'name', 'email', 'created_at'}
ORDER_COLUMNS = {'id', 'name'}
def safe_query(table, columns, order_by):
if table not in ALLOWED_TABLES:
raise ValueError(f'非法的表名: {table}')
selected = [c for c in columns if c in ALLOWED_COLUMNS]
if order_by not in ORDER_COLUMNS:
raise ValueError(f'非法的排序列: {order_by}')
sql = f"SELECT {', '.join(selected)} FROM {table} ORDER BY {order_by}"
cursor.execute(sql)事务管理
import pymysql
conn = pymysql.connect(
host="localhost", port=3306,
user="root", password="root1234", database="vega"
)
try:
cursor = conn.cursor()
conn.begin() # 显式开启事务
cursor.execute("INSERT INTO t_type(type) VALUES(%s)", ('test1',))
# 更多操作...
conn.commit()
print('事务提交成功')
except Exception as e:
print(f'发生错误,回滚: {e}')
conn.rollback()
finally:
cursor.close()
conn.close()事务 ACID 特性
| 特性 | 含义 | MySQL InnoDB 实现 |
|---|---|---|
| 原子性 (Atomicity) | 事务要么全部成功,要么全部失败 | rollback 回滚 + undo log |
| 一致性 (Consistency) | 事务前后数据满足所有约束 | 外键、CHECK、触发器 |
| 隔离性 (Isolation) | 并发事务互不干扰 | MVCC + 行级锁 |
| 持久性 (Durability) | 提交后数据永久保存 | redo log + binlog 双写 |
连接池
数据库连接是昂贵的资源——TCP 三次握手、MySQL 认证握手、线程分配。频繁创建和销毁连接会显著降低性能。连接池通过 预创建 + 复用 机制解决此问题。
pip install dbutilsimport pymysql
from dbutils.pooled_db import PooledDB
class MySQLPool:
"""线程安全的 MySQL 连接池"""
def __init__(self, host, port, user, password, database,
mincached=5, maxcached=20, maxconnections=50):
self._pool = PooledDB(
creator=pymysql,
maxconnections=maxconnections, # 最大连接数上限
mincached=mincached, # 初始化时创建的闲置连接
maxcached=maxcached, # 最大闲置连接数
blocking=True, # 连接耗尽时阻塞等待
host=host,
port=port,
user=user,
password=password,
database=database,
charset='utf8mb4'
)
def get_conn(self):
"""从池中获取一个连接"""
return self._pool.connection()
# 全局单例
mysql_pool = MySQLPool(
host="localhost", port=3306,
user="root", password="root1234",
database="vega"
)
# 使用连接池
conn = mysql_pool.get_conn()
try:
with conn.cursor() as cursor:
cursor.execute('SELECT * FROM t_user')
for row in cursor.fetchall():
print(row)
finally:
conn.close() # 归还到池中,而非真正关闭 TCP 连接连接池关键参数
| 参数 | 含义 | 建议值 |
|---|---|---|
maxconnections | 最大连接数上限 | 根据 MySQL max_connections 设置,通常 20-50 |
mincached | 池初始化时创建的闲置连接 | 5-10 |
maxcached | 最大闲置连接数 | 与 maxconnections 一致 |
blocking | 连接耗尽时是否等待 | True(生产推荐) |
ping | 借出前检测连接有效性 | 1(每次 ping,生产推荐) |
SQLAlchemy ORM
ORM (Object-Relational Mapping) 将数据库表映射为 Python 类,将 SQL 操作转化为面向对象的 API 调用。
pip install sqlalchemy声明式映射
from sqlalchemy import Column, String, ForeignKey, create_engine
from sqlalchemy.orm import sessionmaker, relationship, declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = 'user'
id = Column(String(20), primary_key=True)
name = Column(String(20))
# 一对多关系:一个 User 拥有多个 Book
books = relationship('Book', back_populates='owner')
class Book(Base):
__tablename__ = 'book'
id = Column(String(20), primary_key=True)
name = Column(String(20))
user_id = Column(String(20), ForeignKey('user.id'))
owner = relationship('User', back_populates='books')CRUD 操作
# 初始化引擎与会话
engine = create_engine(
'mysql+mysqlconnector://root:root1234@localhost:3306/test',
echo=False, # 不打印 SQL 日志
pool_size=10, # 内置连接池大小
max_overflow=20, # 超出 pool_size 的最大额外连接
)
Session = sessionmaker(bind=engine)
session = Session()
# --- Create ---
new_user = User(id='5', name='Bob')
session.add(new_user)
session.commit()
# --- Read ---
user = session.query(User).filter(User.id == '5').one()
print(f'type: {type(user)}, name: {user.name}')
# 条件查询 + 排序 + 分页
users = (
session.query(User)
.filter(User.name.like('B%'))
.order_by(User.name)
.limit(10)
.all()
)
# --- Update ---
user.name = 'Bob Updated'
session.commit() # ORM 自动跟踪脏数据
# --- Delete ---
session.delete(user)
session.commit()
session.close()关系查询
# 查询 User 时自动加载其所有 Book
user = session.query(User).filter(User.id == '5').one()
for book in user.books:
print(f'{user.name} 拥有书籍: {book.name}')
# 从 Book 反向查询 Owner
book = session.query(Book).first()
print(f'《{book.name}》的所有者是: {book.owner.name}')Engine URL 格式
dialect+driver://username:password@host:port/database| 驱动 | URL 前缀 |
|---|---|
| mysql-connector-python | mysql+mysqlconnector:// |
| PyMySQL | mysql+pymysql:// |
| PostgreSQL | postgresql+psycopg2:// |
| SQLite | sqlite:///path/to/db |
实战案例:数据库导出到 Excel
pip install openpyxl pymysqlimport openpyxl
import pymysql
workbook = openpyxl.Workbook()
sheet = workbook.active
sheet.title = '员工基本信息'
sheet.append(('工号', '姓名', '职位', '月薪', '补贴', '部门'))
conn = pymysql.connect(
host='127.0.0.1', port=3306,
user='guest', password='Guest.618',
database='hrs', charset='utf8mb4'
)
try:
with conn.cursor() as cursor:
cursor.execute(
'SELECT eno, ename, job, sal, COALESCE(comm, 0), dname '
'FROM tb_emp NATURAL JOIN tb_dept'
)
for row in cursor:
sheet.append(row)
workbook.save('hrs.xlsx')
print('导出成功: hrs.xlsx')
except pymysql.MySQLError as err:
print(f'数据库错误: {err}')
finally:
conn.close()驱动选择决策树
常见陷阱
| 陷阱 | 说明 | 正确做法 |
|---|---|---|
| 忘记 commit | InnoDB 默认不自动提交 | 显式 commit() 或设置 autocommit=True |
| 不关连接 | 连接泄漏导致 "Too many connections" | try/finally 或 with 语句 |
| 字符串拼接 SQL | SQL 注入漏洞 | 始终使用 %s 参数化查询 |
| 逐条插入大量数据 | 每条一个事务,极慢 | executemany() + 单事务 |
| 不设字符集 | 中文乱码(默认 latin1) | charset='utf8mb4' |
| 连接不设超时 | 断连后长时间等待 | connect_timeout + read_timeout |
| 硬编码密码 | 安全风险 | 环境变量或配置文件 |
术语表
| 术语 | 定义 |
|---|---|
| ORM | Object-Relational Mapping,对象-关系映射 |
| Session | SQLAlchemy 中的工作单元,管理对象持久化 |
| Engine | SQLAlchemy 中的数据库连接核心 |
| Connection Pool | 连接池,预创建并复用数据库连接 |
| Parameterized Query | 参数化查询,用占位符代替字符串拼接 |
| ACID | 事务四大特性:原子性、一致性、隔离性、持久性 |
| MVCC | Multi-Version Concurrency Control,多版本并发控制 |
延伸阅读
版本差异(标准库 → Python 3.14)
| 模块/特性 | 本文编写时 | Python 3.14 变化 |
|---|---|---|
datetime | utcnow() / utcfromtimestamp() | 3.12 起弃用,改用 datetime.now(tz=datetime.UTC) / fromtimestamp(ts, tz=datetime.UTC)(aware 对象) |
asyncio | 基础 API | 3.14 新增内省能力(asyncio.Task/Future 状态查询);3.11 起推荐 TaskGroup + asyncio.timeout() |
typing | 旧式 List/Dict | 3.9+ 内置泛型;3.10+ 联合类型 X | Y;3.12 type 语句;3.14 PEP 649 延迟注解 |
importlib | imp 模块 | imp 于 3.12 移除,统一使用 importlib |
| 压缩 | zlib/gzip/bz2/lzma | 3.14 新增 zstandard 标准库支持(PEP 784) |
pathlib | 基础路径操作 | 3.12+ 持续增强(Path.walk() 等),3.13 支持 is_relative_to() 等 |
| 往事清理 | — | 3.13 移除 cgi、telnetlib、crypt、audioop 等已废弃模块 |
本文讲解的模块核心 API 与使用模式在 3.14 中保持稳定;注意上述弃用/移除项,升级时优先用标准库推荐的替代方案。