pandas 1.5 升级 SQLAlchemy 2 后读库报 ObjectNotExecutableError
pandas 1.5 与 SQLAlchemy 2 组合读取数据库报不可执行对象,核对堆栈,用 Connection 和 text 参数绑定修复,并区分当前版本支持情况。
旧项目使用 pandas 1.5.3 读取 MySQL,升级 SQLAlchemy 到 2.0 后,pd.read_sql(sql_string, connection) 报 ObjectNotExecutableError: Not an executable object,前面还出现 'str' object has no attribute '_execute_on_connection'。这类堆栈指向 SQLAlchemy 执行接口接收了普通字符串,先核对依赖组合和传入的 SQL 对象。
这里讨论的是已经取得 SQLAlchemy Connection 后的旧版 pandas 兼容问题。数据库连接超时、SQL 语法错误或表不存在,需要按对应错误处理。
从堆栈确认字符串在哪一层失败
检查实际运行进程里的 pandas 和 SQLAlchemy 版本,而不是另一个终端或虚拟环境的安装结果。下面是可放在同一解释器中的只读检查示例:
import pandas as pd
import sqlalchemy
print(pd.__version__)
print(sqlalchemy.__version__)
如果堆栈从 pandas 的 SQL 读取代码进入 Connection.execute(),随后查找 _execute_on_connection 失败,说明这次调用还没把语句按支持的形式交给数据库执行。反复修改 SQL 里的空格、连接池回收时间或 MySQL 用户权限,不会解决这项接口差异。
SQLAlchemy 2.0 的 Connection.execute() 要求可执行的语句对象;普通 SQL 字符串可包装为 text()。当前 pandas 的 read_sql() 本身仍接受 SQL 字符串和 SQLAlchemy 语句对象,不能据此写成“pandas 从此不支持字符串”。
保留 Connection,把查询包装成 text
以下是替换旧调用的示例,假设 engine 已按项目配置创建,账号具备必要的只读权限:
import pandas as pd
from sqlalchemy import text
query = text("SELECT :value AS check_value")
with engine.connect() as connection:
result = pd.read_sql(query, connection, params={"value": 1})
print(result)
这里的 :value 由参数字典绑定,预期结果有 check_value 列及值 1,用于确认读取链路恢复。实际业务查询仍需使用正确的表名、列名和权限,不能只以这条检查成功作为全部业务通过。
替换业务查询时,将原 SQL 放进 text(),把可变的值通过 params 传递。text() 不会自动净化拼接进去的用户输入,也不能把表名或列名当作普通值参数绑定;动态标识符应由应用允许的名称或 SQLAlchemy 表达式构造。
保持 with engine.connect() 的连接范围,读取结束后释放连接。若使用 chunksize 返回迭代器,应在连接仍开放时完成读取,避免把迭代器拿到连接范围外才消费。此处是读取兼容修复,不需要增加提交、修改表结构或放宽数据库权限。
升级依赖时,按组合验证
如果实际错误是 'OptionEngine' object has no attribute 'execute',调用经过的是已移除的引擎执行接口,不能只包装 SQL 就认定修复。先确认 pandas 是否将引擎直接用于旧式执行;显式 Connection 和 text() 可用于核对这条旧代码路径,项目仍需处理完整依赖兼容。
准备升级时,选择与当前 Python、驱动及其他依赖兼容的 pandas、SQLAlchemy 组合,在隔离环境验证查询结果、日期解析及空值类型,再更新锁文件与部署。当前 pandas 已支持 SQLAlchemy 2,旧故障不应成为长期锁死旧依赖的理由。
若短期必须回退,恢复此前留存的依赖锁文件和应用版本;单独指定一个历史 SQLAlchemy 版本,未必满足其他库的要求。不要修改安装目录里的 pandas 源码来绕过执行检查,这种改动容易被后续安装覆盖。
验收应使用应用实际解释器,既确认只读检查不再出现不可执行对象错误,也核对业务查询的行数、列名和数据类型。若升级后的结果不符合业务预期,先恢复保留的依赖组合,再定位读取差异;不需要重建数据库。


