根本原因是未传入有效的数据库连接对象;pd.read_sql()需SQLAlchemy Engine实例,而非URL字符串或Connection对象,且须确保字符集(utf8mb4)、时区配置正确,并用chunksize分块处理大表。

为什么 pd.read_sql() 读 MySQL 总是报 TypeError: 'NoneType' object is not callable
根本原因是没传对数据库连接对象——pd.read_sql() 要的是一个能执行 SQL 的“连接句柄”,不是 SQLAlchemy 的 Engine 或 Connection 对象本身,更不是字符串 URL。
常见错误写法:pd.read_sql("SELECT * FROM users", "mysql://user:pass@localhost/db") —— 这里第二个参数是 URL 字符串,但 read_sql() 不会自动解析它,直接当连接对象用就崩了。
- 正确做法:必须先用 SQLAlchemy 创建
Engine,再把它传进去(Engine支持connect()方法,所以被接受) - 不推荐用
create_engine(...).connect()后的结果传入:那是Connection对象,虽然能用,但容易漏关连接、引发连接泄漏 - 如果用的是 PyMySQL 或 mysqlclient 底层驱动,确保已安装对应包(
pip install PyMySQL),否则create_engine会静默 fallback 到不兼容的驱动
from sqlalchemy import create_engine
import pandas as pd
<p>engine = create_engine("mysql+pymysql://user:pass@localhost:3306/db")
df = pd.read_sql("SELECT id, name FROM users LIMIT 10", engine)
read_sql_query() 和 read_sql_table() 该选哪个?
二者底层都调用 read_sql(),但语义和限制不同,选错会导致意外行为或报错。
-
read_sql_query():只接受 SQL 查询字符串,比如SELECT、带WHERE或子查询的语句;不能填表名,否则报DatabaseError: Execution failed on sql... -
read_sql_table():只接受表名(字符串),内部拼SELECT * FROM {table};不支持 JOIN、WHERE、别名,也不能读视图(部分数据库不支持) - 性能上没本质区别,但
read_sql_table()在某些方言下会额外查元数据(比如字段类型),略慢一点;而复杂查询必须用read_sql_query()
如果你要加条件、分页、聚合,老老实实用 read_sql_query();如果只是全量导出一张小表,read_sql_table() 写起来少几个字符,但别指望它更高效。
MySQL 中文乱码、datetime 字段变 NaT 怎么办?
这不是 Pandas 的锅,是连接层编码和时区没对齐。SQLAlchemy 默认不强制设置字符集和时区,MySQL 客户端协议一松懈,数据就变形。
- 在连接 URL 末尾加上
?charset=utf8mb4(不是utf8!MySQL 的utf8实际是 utf8mb3,不支持 emoji) - 显式指定时区:URL 加
&timezone=UTC,或创建 engine 时传connect_args={"timezone": "UTC"} - 如果 MySQL 服务端时区是
+08:00,而 Python 环境默认 UTC,DATETIME字段可能被错误转换成 NaT,尤其配合parse_dates参数时 -
pd.read_sql(..., parse_dates=["created_at"])对NULL值敏感,字段含空值时建议先用dtype指定为string,再手动转
engine = create_engine(
"mysql+pymysql://user:pass@localhost:3306/db?charset=utf8mb4&timezone=UTC"
)大表怎么读才不 OOM?
直接 read_sql("SELECT * FROM huge_table", engine) 很容易把内存吃光,尤其字段多、文本长的时候。Pandas 本身不支持流式读取,得靠分块绕过去。
- 用
chunksize参数(单位是行数),返回的是TextFileReader迭代器,每次只载入一块: - 别用
for df in pd.read_sql(..., chunksize=10000)然后拼pd.concat——这等于又全加载进内存了 - 真正省内存的做法:每块单独处理(比如清洗后存 CSV / 写数据库 / 统计中间结果),不保留原始块
- 注意:
chunksize对read_sql_table()有效,但对含ORDER BY或LIMIT的read_sql_query()无效(SQL 层无法分块)
如果真要分页查大表,得自己写带 OFFSET/LIMIT 的循环,或者用主键范围(如 id BETWEEN ? AND ?)来切片,避免深分页性能坍塌。










