执行脚本时,报AttributeError: 'Cursor' object has no attribute 'cursor',如下图所示:

一、问题分析:
在实际代码中使用的是:
with db.cursor() as cursor:
显然curcor已经是游标对象,且后续代码中没有再出现cursor.cursor.所以推测是其他地方调用了get_token或者上游函数,有类似xxx.cursor(),但是xxx已经是Cursor对象,不是数据库连接db对象。结合脚本运行平台为并发运行,所以推测是exec 沙箱命名污染导致
二、问题排查
1、通过日志的方式打印出
python
def get_token(self):
with pymysql.connect(host=HOST_MYSQL, port=PORT_MYSQL, user=USER_MYSQL,
password=PASSWORD_MYSQL, db=DB_MYSQL) as db:
print(f"==== db type: {type(db)} ====") # 正常应该输出 <class 'pymysql.connections.Connection'>
with db.cursor() as cursor:
print(f"==== cursor type: {type(cursor)} ====")
sql = "SELECT value FROM test_iot.name_value where name = %s"
cursor.execute(sql, (f"{self.env_type}_token",))
data = cursor.fetchone()
if not data:
raise Exception(f"数据库中不存在{self.env_type}环境的token配置,请先手动初始化一条记录")
return data[0]
重新运行

with pymysql.connect(...) as db: 这一行执行完,as 赋值出来的 db 变量居然是 Cursor 游标对象 ,不是 Connection 连接对象。结合日志推测在脚本别的地方,提前存在一个名叫connect的变量,他被覆盖成db.cursor,从而导致pymysql.connect被偷偷劫持了。
2、原因剖析 :
exec(user_script, param_dict)中param_dict是全局变量,当脚本运行到pymysql.connect,python优先去全局param_dict找pymysql;拿到pymysql对象再找它的connect属性。
3、问题解决
函数内部import pymysql ,绕开全局污染。
不依赖connect的上下文管理器,手动try-finally关闭连接,避开pymysql connect 上下文被污染。
python
def get_token(self):
import pymysql
conn = pymysql.connect(host=HOST_MYSQL, port=PORT_MYSQL, user=USER_MYSQL,
password=PASSWORD_MYSQL, db=DB_MYSQL)
try:
print(f"==== conn type: {type(conn)} ====")
with conn.cursor() as cursor:
sql = "SELECT value FROM test_iot.name_value where name = %s"
cursor.execute(sql, (f"{self.env_type}_token",))
data = cursor.fetchone()
if not data:
raise Exception(f"数据库中不存在{self.env_type}环境的token配置,请先手动初始化一条记录")
return data[0]
finally:
conn.close()
三、总结
1、pymysql Connection 不能多线程共用
每次进入函数connect,本身是安全的,但是pymysql底层版本bug会出现对象错乱。
2、exec沙箱尽量不要多线程同时调用数据库函数
exec(user_script, param_dict) 动态执行脚本,尽量不要使用全局变量名 db、cursor,所有数据库连接都放在函数内部作为局部变量,减少命名串扰。
补充:
exec沙箱是用Python内置exec函数,传入自定义globals字典,给用户脚本单独分配一套独立全局名字空间,用来隔离变量。