向MariaDB存储/读取Pickle对象时遇UnpicklingError问题求助
Pickle序列化对象存入MariaDB后反序列化报错:_pickle.UnpicklingError: unpickling stack underflow
问题重现
执行读取操作时触发反序列化错误:
Traceback (most recent call last): File "read_pickle_data.py", line 49, in <module> unpickled = pickle.loads(item[0].encode()) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ _pickle.UnpicklingError: unpickling stack underflow
环境信息:
- 数据库:MariaDB
- Python版本:3.11
- Pickle协议:尝试过1、2及
pickle.HIGHEST_PROTOCOL
错误原因
你的代码存在两个关键问题:
- 插入时错误处理二进制数据:手动使用
conn.escape_string()处理pickle序列化后的二进制数据,再通过字符串拼接SQL语句,导致原始二进制中的特殊字符被转义(比如\n变成\\n),存储到数据库的已经不是完整的原始pickle数据。 - 读取时重复损坏数据:读取后又调用
conn.escape_string(),再次转义已经被损坏的数据,还尝试用encode()处理,完全偏离了原始的pickle二进制格式。
从日志也能看出:插入后的car_obj_escaped已经把原始的\n转成了\\n,读取后的pickled_obj里的十六进制字符(比如\x01)变成了x01,数据结构完全被破坏,自然无法反序列化。
修复方案
1. 确保数据库字段类型正确
把存储pickle数据的字段car_pickled_object设置为BLOB类型(比如MEDIUMBLOB),专门用于存储二进制数据,避免字符编码转换导致的损坏。
2. 插入操作:使用参数化查询,避免手动转义
不要用字符串拼接SQL,改用MySQLdb的参数化查询,让驱动自动处理二进制数据的存储,不需要手动调用escape_string()。
3. 读取操作:直接使用原始二进制数据
读取后直接获取字段的二进制值,不需要任何额外的转义或编码操作,直接传给pickle.loads()。
修正后的完整代码
import MySQLdb import pickle class Car: def __init__(self, model, year, color): self.model = model self.year = year self.color = color def display_info(self): return f"{self.year} {self.color} {self.model}" conn = MySQLdb.connect(host="<host>", port=<port>, user="<user>", password="<passwd>", database="<db>") cursor = conn.cursor() task = "read" if task == "insert": car = Car("123", 2024, "red") car_obj_serialized = pickle.dumps(car, protocol=pickle.HIGHEST_PROTOCOL) # 使用参数化查询,不需要手动escape query = """INSERT INTO car_models(car_pickled_object, status) VALUES(%s, %s);""" # 直接传入二进制数据作为参数 cursor.execute(query, (car_obj_serialized, "Booked")) conn.commit() else: query = """SELECT car_pickled_object FROM car_models WHERE id=10""" cursor.execute(query) row = cursor.fetchone() if row: # 直接获取二进制数据,不需要转义或encode pickled_obj = row[0] unpickled_obj = pickle.loads(pickled_obj) print(unpickled_obj.display_info()) # 验证反序列化结果 cursor.close() conn.close()
验证说明
- 插入时,
car_obj_serialized是原始的pickle二进制数据,参数化查询会自动将其写入BLOB字段,不会做任何转义。 - 读取时,从数据库获取的
pickled_obj就是原始的二进制数据,直接用pickle.loads()即可成功反序列化出Car对象。
内容的提问来源于stack exchange,提问作者Sanket
相关产品推荐
相关产品推荐

