第 1 章 数据库连接配置
从一个生活场景说起
想象你经营着一家餐厅,食材都存放在城郊的一座仓库里。每当厨师要做菜,就得派人去仓库取货。
- 方案 A:每次取货都临时雇一辆新车、现修一条路过去,用完就扔掉——效率低得离谱。
- 方案 B:养一支固定的运输队,车辆和路线随时待命,用完归还、下次再用——高效且可控。
在程序世界里:
- 仓库 = MySQL 数据库服务器;
- 派人去仓库 = 程序向数据库发起一次"连接(Connection)";
- 方案 A = 每次执行 SQL 都新建连接,用完关闭(开销极大);
- 方案 B 的运输队 = 连接池(Connection Pool);
- 管理运输队的调度中心 = Engine(引擎),也就是本章的主角。
理解了"Engine = 调度中心 + 运输队",这一章就学会了一大半。
核心概念
什么是数据库连接?
MySQL 是一个独立运行的程序(通常监听在 3306 端口),你的 Python 程序是另一个程序。两个程序之间要通信,必须先建立一条"通道",这就是连接。
建立连接的过程类似打电话:拨号(TCP 握手)→ 验证身份(用户名密码认证)→ 才能开始通话(执行 SQL)。这个过程要耗费几十甚至上百毫秒——对只执行 1 毫秒就能完成的 SQL 来说,"打电话"的成本远超"通话"本身。
什么是 Engine(引擎)?
Engine 是 SQLAlchemy 连接数据库的"总闸",由 create_engine() 创建。它本身不直接干活,而是负责:
- 记住数据库在哪里(URL);
- 在内部维护一个连接池,管理一批可复用的连接;
- 当你要执行 SQL 时,从池子里分配一条连接给你。
一个应用程序通常只需要一个 Engine,全局共享。
什么是连接池(Connection Pool)?
连接池就是预先建好若干条连接放在"池子"里:
- 要执行 SQL → 从池子借一条连接;
- 用完了 → 归还到池子(注意:是归还,不是关闭!);
- 池子里的连接可以被反复借用,避免了反复"打电话"的开销。
数据库 URL:数据库的"快递地址"
SQLAlchemy 用一串 URL 字符串描述数据库的位置,格式为:
数据库类型+驱动名://用户名:密码@主机:端口/数据库名?参数把我们的连接串拆开看,就像拆一个快递地址:
mysql+pymysql://root:123456@localhost:3306/demo_db?charset=utf8mb4
│───────│ │───│ │────│ │───────────│ │─────│ │──────────────│
① ② ③ ④ ⑤ ⑥| 编号 | 部分 | 含义 | 快递类比 |
|---|---|---|---|
| ① | mysql | 数据库种类(术语叫方言 dialect) | 哪个城市 |
| ② | pymysql | 驱动名(Python 与 MySQL 之间的"翻译官") | 用哪家快递公司 |
| ③ | root:123456 | 用户名和密码 | 收件人的身份凭证 |
| ④ | localhost:3306 | 主机地址和端口 | 街道和门牌号 |
| ⑤ | demo_db | 具体哪个数据库 | 哪一栋楼 |
| ⑥ | charset=utf8mb4 | 通信使用的字符编码 | 双方约定用什么语言交谈 |
术语解释 —— 驱动(Driver):SQLAlchemy 本身不直接和 MySQL 通信,它把指令交给一个叫"驱动"的底层库去执行。
pymysql就是最常用的 MySQL 驱动,需要用pip install pymysql单独安装。
示例代码(逐行讲解)
pip install sqlalchemy pymysqlfrom sqlalchemy import create_engine
# MySQL 连接:root 是用户名,123456 是密码,demo_db 是数据库名
engine = create_engine(
"mysql+pymysql://root:123456@localhost:3306/demo_db?charset=utf8mb4",
echo=True, # 打印所有执行的 SQL,学习阶段建议打开
pool_size=5, # 连接池常驻连接数(运输队的固定编制:5 辆车)
max_overflow=10, # 高峰期允许额外创建的连接数(忙不过来可临时加 10 辆)
pool_recycle=3600, # 连接存活 1 小时后回收重建(避开 MySQL 默认 8 小时断连)
pool_pre_ping=True, # 取连接前先探测是否有效,防止拿到"死连接"
)逐行说明:
create_engine(url):创建引擎。注意这一步只是"登记"了数据库地址,并没有真正拨号。echo=True:让 SQLAlchemy 把每一条实际执行的 SQL 打印到控制台。学习阶段这是最好的老师——你能清楚看到自己的 Python 代码被翻译成了什么 SQL。pool_size=5:池子里常备 5 条连接。max_overflow=10:5 条都不够用时,允许临时再多建 10 条;高峰过后多出的会被关闭。pool_recycle=3600:MySQL 有个"脾气"——一条连接闲置超过wait_timeout(默认 28800 秒,即 8 小时)就会被服务端单方面挂断。让连接活满 1 小时就主动退休换新,可以避开这个问题。pool_pre_ping=True:每次从池子借连接前,先轻轻"戳一下"(发送一个廉价的 ping),如果连接已被服务端断开,就换一条新的。生产环境强烈建议开启。
注意:
create_engine不会自动创建数据库,需要先在 MySQL 中手动建好:sqlCREATE DATABASE demo_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
关键知识点
| 概念 | 说明 |
|---|---|
create_engine() | 创建引擎,参数为数据库 URL |
echo=True | 控制台输出实际执行的 SQL,便于调试 |
| 连接池 | Engine 自动管理,复用连接避免频繁建连 |
pool_size | 池内常驻连接数 |
max_overflow | 超出 pool_size 后允许临时新增的连接数 |
pool_recycle | 定期回收连接,避免 MySQL wait_timeout 断连 |
pool_pre_ping | 使用前探测连接有效性,生产环境推荐开启 |
| URL 格式 | mysql+pymysql://user:password@host:port/dbname?charset=utf8mb4 |
深入理解:连接池的一天
用"运输队"比喻串一遍完整流程:
- 程序启动,
create_engine()创建了调度中心,但一辆车都还没雇(懒初始化); - 第一条 SQL 来了,调度中心发现池子是空的,才雇了第 1 辆车(真正建立连接);
- SQL 执行完,车回到车库待命(连接归还而非关闭);
- 并发来了 8 个请求,5 辆常备车不够,临时加雇 3 辆(不超过
pool_size + max_overflow); - 一辆车跑了满 1 小时,调度中心让它退休、换辆新的(
pool_recycle); - 每次派车前先检查车况,坏了的直接报废换新车(
pool_pre_ping)。
进阶内容
1. 不要把密码写死在代码里。 真实项目中连接串应放在环境变量或配置中心:
import os
from sqlalchemy import create_engine
engine = create_engine(os.environ["DATABASE_URL"])2. 异步引擎预告。 高并发场景(如 FastAPI 异步视图)可用异步引擎,写法几乎一样,只是换成 create_async_engine 和异步驱动 aiomysql,详见第 17 章。
3. 一个进程一个 Engine。 Engine 是线程安全的,可以多线程共享;但不要每个请求都 create_engine(),那样等于每来一个客人就新组建一支运输队,完全失去了连接池的意义。
注意事项
create_engine()不会立刻连接数据库,真正建立连接发生在第一次执行 SQL 时。- 生产环境不要把
echo=True打开,会产生大量日志。 - 密码中的特殊字符(如
@、#、/)需要做 URL 编码,例如@写成%40。 - 务必指定
charset=utf8mb4,否则中文和 emoji 会存储失败或乱码。 - MySQL 默认 8 小时无活动会断开连接(
wait_timeout),pool_recycle应小于该值。 - 连接池上限(
pool_size + max_overflow)不要超过 MySQL 的max_connections(默认 151),否则会出现"Too many connections"错误。
本章小结
- Engine = 连接数据库的调度中心,全局一个就够;
- 连接池 = 可复用连接的集合,"借-用-还"三步避免重复建连;
- 数据库 URL = 数据库的快递地址,各段各司其职;
- 记住四个池参数:
pool_size、max_overflow、pool_recycle、pool_pre_ping。