在Python中获取更新的MySQL表条目而不关闭连接

2 投票
2 回答
2621 浏览
提问于 2025-04-18 11:04

我有两个Python程序在同时运行。第一个程序(Script1)定期往一个表里写数据,第二个程序(Script2)则从同一个MySQL表里读取数据。两个程序都是同时在运行的。Script2需要获取表中最新添加的数据。

现在的问题是,Script1往表里添加数据的过程很顺利,但Script2每次都无法读取到最新的数据。只有在我关闭连接后再重新打开时,Script2才能读取到最新的条目。

这是唯一的解决办法吗?有没有其他方法可以在不每次关闭和打开连接的情况下获取更新的数据?程序员在访问一个不断更新的数据库时,通常遵循什么最佳实践?

更详细地说:

下面的代码运行得很好,但无法显示更新后的数据。第一次调用时,它能成功显示最后一条数据,但在接下来的几次调用readComm()时,尽管表已经更新,显示的仍然是同一条数据。

import MySQLdb
import time

db = MySQLdb.connect("localhost", "root", "abc", "abc")
cursor=db.cursor()

def readComm():
    sql = "SELECT * FROM my_table ORDER BY id DESC LIMIT 1;"
    try:
        cursor.execute(sql)
        # Fetch all the rows in a list of lists.
        results = cursor.fetchall()
        print '~~~~', results
    except:
        print "Error! Unable to fetch data" 
    return
for i in range(5):
    readComm()
    time.sleep(10)

如果我修改代码,让它在进入和退出readComm()时分别打开和关闭数据库连接,那么它就能显示更新后的数据。

2 个回答

2

正如@DanielRoseman所提到的,你在一个事务中写入数据,这个事务的目的是让你在发生错误时可以撤销一系列的更改。在这个过程中,事务中的更改在完成之前是对外不可见的,直到你通过执行COMMIT语句将这些更改变为永久的。即使你只是用SELECT语句读取数据,你也会开始一个事务——所以你的“读取”脚本每次查看的都是第一次SELECT时数据库的状态。

最简单的解决办法是使用与Connection对象相关的commit()方法。不过,更优雅的解决方案是利用MySQLdb的Connection对象的上下文管理器协议,这个协议是从PEP 343中引入的:

def __enter__(self):
   if self.get_autocommit():
       self.query("BEGIN")
   return self.cursor()

def __exit__(self, exc, value, tb):
   if exc:
       self.rollback()
   else:
       self.commit()

这段代码告诉你Connection对象在与with语句一起使用时的行为。所以,如果你这样使用你的连接对象db

with db as x:
    # indented code block here

那么会发生以下情况:

  • x(或者你选择的名字)会绑定到db.__enter__()返回的Cursor对象上*
  • 如果在缩进的代码块中抛出异常,db会调用自己的rollback()方法
  • 否则,db会在离开缩进的代码块时调用自己的commit()方法

换句话说,这个模块的设计是为了让你可以轻松实现事务,只需将每一组应该作为单个事务的语句放入with块中。由于你在这里展示的代码只是从表中读取数据,真正需要修改的是另一个脚本——那个对数据库进行更改的脚本,无论你决定使用with还是显式调用commit()

最简单的做法...

...对于“读取”脚本来说,就是在打开连接后调用db.autocommit(True)来启用自动提交模式。Python数据库API规定“如果数据库支持自动提交功能,这个功能必须最初是关闭的”,但如果你不担心并发问题(这应该是你的“读取”脚本的情况),你完全可以将其打开。

实际上,如果这两个脚本是与服务器交互的唯一内容,并且你在另一个脚本中不需要事务,那么最简单的做法就是在两个脚本中都打开自动提交,之后就不用再担心了。但如果你忘记了这里的设置,后来又写了需要进行并发事务的其他脚本,这可能会给你带来麻烦。

*请注意,db.__enter__()为你创建的游标并不会被db.__exit__()关闭。在这种情况下,MySQLdb.Cursor实际上只是一个模拟游标的Python对象;它不会占用额外的服务器资源,你通常不需要担心关闭它。实际上,只要其父Connection保持打开状态,你可以在with块退出后继续使用你在with语句中绑定的游标对象的名字。(当然,除非你显式地close()游标或将其名字绑定到另一个对象。)

5

这只是因为在一个事务中有读取隔离的原因。在每次循环后都要执行 db.commit()

撰写回答