1. 从一次本地脚本连不上库说起
Python 连接数据库这件事,说简单也简单,一行create_engine就能跑;说坑也多,尤其是从本地脚本过渡到轻量服务的时候,连接池、事务提交、游标关闭,每一个环节都可能让你在凌晨两点对着报错发呆。这篇就围绕create_engine和conn.cursor这条链路,把配置骨架和验证动作拆开讲清楚,面向的是本地脚本和轻量服务场景,不涉及重型 ORM 架构。
核心检索词先摆出来:create_engine是 SQLAlchemy 提供的引擎工厂,负责管理连接池和方言适配;conn.cursor是拿到连接后执行 SQL 的游标对象。两者配合,构成了 Python 操作数据库最基础也最通用的路径。适合谁?适合写数据同步脚本、做轻量 API 后端、跑定时任务的开发者,尤其是那些不想引入完整 ORM、但又需要连接复用的场景。
我试过在同一个项目里混用pd.read_sql_query和原生cursor.execute,结果因为事务没提交,读到的数据一直是旧快照。这类问题不是靠背 API 能解决的,得理解连接的生命周期。下面按「问题场景 → 前置准备 → 可复制配置 → 验证动作 → 排障 → 工具链管理」的顺序展开,每一步都给可运行的代码。
2. TaoToken 前置:统一 Key 与 API 通道管理
在进入数据库配置之前,先解决一个容易被忽略的前置问题:当你的脚本里同时涉及数据库连接和 AI 工具调用时,配置项会散落在各处。环境变量、配置文件、硬编码,时间一长就乱。我的做法是用一个统一的 Key/API 通道来管理这类配置,TaoToken 就是干这个的。
它的定位不是数据库中间件,而是帮你把 AI 工具相关的 Key 和 API 端点集中管理。官网入口在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end ,API 基址是 https://taotoken.net/api 。你可以在控制台里创建和管理 API Keys,把不同项目的 Key 分开,避免一个泄露全线崩。
具体操作路径:先到控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 创建项目,然后在 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 生成密钥。生成后,把 Key 写进环境变量,脚本里通过os.environ读取,不要硬编码在代码里。
如果你需要验证模型连通性,可以用模型对话页面 https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite 快速测试。长期做编码或 Agent 开发的,Coding Plan 页面 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 有更细的配额管理。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,ClaudeCode 相关配置参考 https://taotoken.net/claudecode-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claudecode-anthropic&utm_campaign=rewrite 。
这一步的意义在于:数据库连接串和 AI Key 都走环境变量,脚本部署到不同机器时只改环境变量,不动代码。下面进入数据库配置正题。
3. create_engine 与 conn.cursor 的可复制配置骨架
3.1 安装依赖与连接串格式
先装包。SQLAlchemy 负责引擎和连接池,psycopg2 是 PostgreSQL 的驱动,如果你用 MySQL 就换成 pymysql 或 mysqlclient。
pip install sqlalchemy psycopg2-binary pandas连接串的格式是dialect+driver://user:password@host:port/database。以 PostgreSQL 为例:
import os from sqlalchemy import create_engine DB_USER = os.environ.get("DB_USER", "postgres") DB_PASS = os.environ.get("DB_PASS", "your_password") DB_HOST = os.environ.get("DB_HOST", "127.0.0.1") DB_PORT = os.environ.get("DB_PORT", "5432") DB_NAME = os.environ.get("DB_NAME", "testdb") conn_str = f"postgresql+psycopg2://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{DB_NAME}" engine = create_engine( conn_str, pool_size=5, max_overflow=10, pool_pre_ping=True, pool_recycle=1800, echo=False, )这里几个参数值得说明。pool_size=5是连接池常驻连接数,max_overflow=10是高峰期允许临时超出的连接数,两者相加是并发上限。pool_pre_ping=True会在每次取连接前发一个轻量探测,避免拿到已经被数据库断开的死连接,这个在长时间运行的脚本里几乎是必开项。pool_recycle=1800让连接半小时回收一次,绕开数据库端的空闲超时。echo=False关掉 SQL 日志,调试时可以临时改成 True。
3.2 用 conn.cursor 执行 SQL 的骨架
拿到 engine 后,有两种用法。一种是engine.connect()拿连接,再开 cursor;另一种是engine.begin()自动管理事务。先看手动管理的版本:
from sqlalchemy import text def query_users(min_id: int): with engine.connect() as conn: with conn.cursor() as cursor: cursor.execute( text("SELECT id, name, email FROM users WHERE id > :min_id"), {"min_id": min_id}, ) rows = cursor.fetchall() for row in rows: print(row.id, row.name, row.email) return rows注意这里用了text()包裹 SQL,并且用命名参数:min_id传值。这是防注入的标准做法,不要用字符串拼接。with语句保证连接和游标都会正确关闭,即使中间抛异常。
3.3 事务提交的两种写法
写操作必须提交,否则数据不会落库。手动提交:
def insert_user(name: str, email: str): with engine.connect() as conn: with conn.cursor() as cursor: cursor.execute( text("INSERT INTO users (name, email) VALUES (:name, :email)"), {"name": name, "email": email}, ) conn.commit()更推荐用engine.begin(),它会在块结束时自动提交,异常时自动回滚:
def insert_user_v2(name: str, email: str): with engine.begin() as conn: conn.execute( text("INSERT INTO users (name, email) VALUES (:name, :email)"), {"name": name, "email": email}, )engine.begin()返回的连接已经处于事务中,块内所有操作要么全成功要么全回滚。对于批量写入,这个模式能省掉手动 commit 的遗漏风险。
3.4 配合 pandas 的读写
如果你习惯用 DataFrame,pd.read_sql_query直接吃 engine:
import pandas as pd from string import Template def read_table(table_name: str) -> pd.DataFrame: sql = Template("SELECT * FROM $table").substitute(table=table_name) return pd.read_sql_query(sql, engine) def write_table(df: pd.DataFrame, table_name: str, mode: str = "append"): df.to_sql(table_name, engine, if_exists=mode, index=False)if_exists支持replace、append、fail三种。增量入库用append,全量覆盖用replace。注意replace会先 drop 再 create,生产环境慎用。
4. 验证请求与成功结果
配置写完,得验证连接真的可用。第一步,探测引擎能否拿到连接:
from sqlalchemy import text def check_connection(): try: with engine.connect() as conn: result = conn.execute(text("SELECT 1 AS ok")) row = result.fetchone() print("连接成功,返回值:", row.ok) return True except Exception as e: print("连接失败:", repr(e)) return False check_connection()成功时输出连接成功,返回值: 1。如果这里就报错,说明连接串、网络或认证有问题,先解决这一步再往下。
第二步,验证游标执行和事务提交。建一张临时表,插入一条数据,再查出来:
def verify_cursor_and_commit(): with engine.begin() as conn: conn.execute(text(""" CREATE TABLE IF NOT EXISTS _conn_test ( id SERIAL PRIMARY KEY, note TEXT ) """)) conn.execute( text("INSERT INTO _conn_test (note) VALUES (:note)"), {"note": "hello"}, ) with engine.connect() as conn: result = conn.execute(text("SELECT note FROM _conn_test ORDER BY id DESC LIMIT 1")) row = result.fetchone() print("最新记录:", row.note if row else None) verify_cursor_and_commit()预期输出最新记录: hello。如果插入后查不到,八成是事务没提交,检查是否用了engine.begin()或手动conn.commit()。
第三步,验证连接池复用。连续取多次连接,观察是否复用:
def check_pool(): ids = [] for _ in range(3): with engine.connect() as conn: ids.append(id(conn.connection.dbapi_connection)) print("底层连接 id:", ids) print("是否复用:", len(set(ids)) < len(ids)) check_pool()如果三次拿到的底层连接 id 有重复,说明池子在复用。全不一样也正常,取决于池子状态和并发情况。
5. 本篇常见错排查
5.1 报错ModuleNotFoundError: No module named 'psycopg2'
驱动没装。PostgreSQL 装psycopg2-binary,MySQL 装pymysql,SQLite 不需要额外驱动。装完确认连接串里的 driver 名和实际安装的一致,比如postgresql+psycopg2://对应 psycopg2。
5.2 报错connection refused或超时
先确认数据库服务在跑,端口对得上。本地开发常见的是 host 写成localhost但数据库只监听127.0.0.1,或者 Docker 容器里数据库端口没映射出来。用telnet host port或nc -zv host port测一下端口通不通。
5.3 插入成功但查不到数据
事务没提交。用engine.connect()时,默认不会自动提交,必须显式conn.commit()。改用engine.begin()可以避免这个坑。另外注意,某些数据库驱动默认开启自动提交,行为不一致,统一用engine.begin()最稳。
5.4 连接池耗尽QueuePool limit of size 5 overflow 10 reached
并发请求超过了池子上限,或者有连接没归还。检查代码里是否所有engine.connect()都用了with语句。如果手动conn = engine.connect()后忘了conn.close(),连接会一直占着。把pool_size和max_overflow调大只是缓解,根治要靠正确释放。
5.5cursor相关报错this result object does not return rows
用cursor.execute执行了 INSERT/UPDATE/DELETE 之后又调fetchall(),这类语句不返回结果集。要么分开处理,要么用engine.begin()执行写操作,读操作单独走查询路径。
5.6 中文乱码
连接串里加字符集参数,比如 MySQL 用?charset=utf8mb4。PostgreSQL 一般由数据库端编码决定,建库时指定 UTF8 即可。pandas 读写时注意encoding参数。
6. 把数据库配置和 AI Key 统一管起来
回到开头提到的配置管理问题。数据库连接串走环境变量,AI 工具的 Key 也走环境变量,两者在部署时统一由环境注入。TaoToken 在这里的角色是帮你集中管理 AI 侧的 Key 和端点,避免每个脚本里散落不同的 Key。
具体做法:在 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 生成项目专属 Key,写进.env文件,脚本用python-dotenv加载。数据库的账号密码同样放.env,两者互不干扰但统一管理。
from dotenv import load_dotenv import os load_dotenv() DB_URL = os.environ["DATABASE_URL"] AI_API_KEY = os.environ["TAOTOKEN_API_KEY"] AI_BASE_URL = os.environ.get("TAOTOKEN_BASE_URL", "https://taotoken.net/api")这样你的脚本里既有数据库连接,又有 AI 调用能力,配置项集中在一处,换机器只改.env。接入细节参考文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,需要长期跑编码任务的可以看 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 。
最后给一个实用技巧:在create_engine外面包一层工厂函数,把连接串从环境变量读取的逻辑收进去,测试时可以传入内存 SQLite 的串做单元测试,不用连真实数据库。这样数据库配置和业务代码解耦,验证动作也能在 CI 里跑。