Python多进程与pyodbc数据库访问“安全吗”?

15 投票
3 回答
9373 浏览
提问于 2025-04-15 14:55

问题:

我遇到了以下错误信息,但不太明白这是什么意思,也不知道该怎么解决:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "C:\Python26\lib\multiprocessing\forking.py", line 342, in main
    self = load(from_parent)
  File "C:\Python26\lib\pickle.py", line 1370, in load
    return Unpickler(file).load()
  File "C:\Python26\lib\pickle.py", line 858, in load
    dispatch[key](self)
  File "C:\Python26\lib\pickle.py", line 1083, in load_newobj
    obj = cls.__new__(cls, *args)
TypeError: object.__new__(pyodbc.Cursor) is not safe, use pyodbc.Cursor.__new__()

情况:

我有一个SQL Server数据库,里面有很多需要处理的数据。我想用多进程模块来并行处理这些工作,以充分利用我电脑的多个核心。我的类结构大致如下:

  • MyManagerClass
    • 这是主类,程序从这里开始运行。
    • 它创建了两个多进程队列,一个叫 work_queue,另一个叫 write_queue
    • 它还创建并启动了其他进程,然后等待它们完成。
    • 注意:这不是 multiprocessing.managers.BaseManager() 的扩展
  • MyReaderClass
    • 这个类负责从SQL Server数据库中读取数据。
    • 它把数据放到 work_queue 中。
  • MyWorkerClass
    • 这个类负责处理工作。
    • 它从 work_queue 中获取数据,并把处理完成的数据放到 write_queue 中。
  • MyWriterClass
    • 这个类负责把处理好的数据写回到SQL Server数据库。
    • 它从 write_queue 中获取数据。

我的想法是有一个管理者、一个读取者、一个写入者和多个工作者。

其他细节:

我在错误输出中看到这个错误信息出现了两次,所以我猜测是读取者和写入者各出现了一次。我的工作进程创建得很好,但它们就在那里等待,直到我按下键盘中断,因为 work_queue 中没有任何东西。

读取者和写入者各自都有自己的数据库连接,这些连接是在初始化时创建的。

解决方案:

感谢Mark和Ferdinand Beyer的回答和问题,帮助我找到了这个解决方案。他们指出了Cursor对象不能被“序列化”,而这是多进程在进程间传递信息时使用的方法。

我代码中的问题在于 MyReaderClass(multiprocessing.Process)MyWriterClass(multiprocessing.Process) 都在它们的 __init__() 方法中连接到数据库。我在 MyManagerClass 中创建了这两个对象(也就是调用了它们的初始化方法),然后调用了 start()

这样就会创建连接和游标对象,然后尝试通过序列化把它们发送到子进程。我的解决方案是把连接和游标对象的创建移动到 run() 方法中,这个方法只有在子进程完全创建后才会被调用。

3 个回答

3

pyodbc 是一个用于 Python 的数据库连接工具,它遵循了 Python 的数据库接口标准(DB-API)。根据这个标准,它的线程安全级别是 1级。这意味着在多个线程中,不能共享同一个数据库连接,而且它根本不安全。

我认为即使底层的 ODBC 驱动程序是线程安全的,这也没有什么影响。问题出在 Python 代码上,就像提到的 Pickling 错误一样。

3

这个错误是在 pickle 模块中出现的,也就是说你的数据库游标对象在某个地方被转存(也就是把它变成可以存储的格式)和恢复(再把它变回Python对象)。

我猜 pyodbc.Cursor 这个对象不支持转存。你为什么要试着保存游标对象呢?

检查一下你在工作流程中是否使用了 pickle,或者它是否被隐含地使用了。

14

多进程编程依赖于“序列化”来在不同的进程之间传递对象。可是,pyodbc的连接和游标对象是不能被序列化的。

>>> cPickle.dumps(aCursor)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python2.5/copy_reg.py", line 69, in _reduce_ex
    raise TypeError, "can't pickle %s objects" % base.__name__
TypeError: can't pickle Cursor objects
>>> cPickle.dumps(dbHandle)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python2.5/copy_reg.py", line 69, in _reduce_ex
    raise TypeError, "can't pickle %s objects" % base.__name__
TypeError: can't pickle Connection objects

“它把东西放进工作队列”,那到底是什么东西呢?有可能游标对象也被传递过去了吗?

撰写回答