在web应用中使用Postgres时的“事务中止”错误

4 投票
1 回答
7091 浏览
提问于 2025-04-15 18:56

最近,我为了提高性能,把我正在开发的一个网页应用从MySQL迁移到了PostgreSQL,因为我需要PostGIS提供的功能。现在,我经常遇到以下错误:

当前事务已中止,命令在事务块结束之前将被忽略

这个服务器应用使用mod_python。错误发生在一个叫做hailing的函数里(也就是为特定客户创建新会话的那个函数)。下面是相关的代码片段(异常发生在调用sessionAppId的那一行):

def hello(req):
req.content_type = "text/json"
req.headers_out.add('Cache-Control', "no-store, no-cache, must-revalidate")
req.headers_out.add('Pragma', "no-cache")
req.headers_out.add('Expires', "-1")
instance = req.hostname.split(".")[0]

cookieSecret = '....' # whatever :-)
receivedCookies = Cookie.get_cookies(req, Cookie.SignedCookie, secret = cookieSecret)
sessionList = receivedCookies.get('sessions', None)
sessionId = str(uuid.uuid4())
if sessionList:
    if type(sessionList) is not Cookie.SignedCookie:
        return "{status: 'error', errno:1, errmsg:'Permission denied.'}"
    else:
        sessionList = sessionList.value.split(",")
        for x in sessionList[:]:
            revisionCookie = receivedCookies.get('rev_' + str(sessionAppId(x, instance)), None)
            # more processing here....
# .....
cursors[instance].execute("lock revision, app, timeout IN SHARE MODE")
cursors[instance].execute("insert into app (type, active, active_revision, contents, z) values ('session', true, %s, %s, 0) returning id", (cRevision, sessionId))
sAppId = cursors[instance].fetchone()[0]
cursors[instance].execute("insert into revision (app_id, type) values (%s, 'active')", (sAppId,))
cursors[instance].execute("insert into timeout (app_id, last_seen) values (%s, now())", (sAppId,))
connections[instance].commit()
# .....

这里是sessionAppId的具体内容:

def sessionAppId(sessionId, instance):
cursors[instance].execute("select id from app where type='session' and contents = %s", (sessionId, ))
row = cursors[instance].fetchone()
if row == None:
    return 0
else:
    return row[0]

一些说明和额外的问题:

  1. cursors[instance]和connections[instance]是为这个域名服务的网页应用的数据库连接和游标。也就是说,同一台服务器同时服务example1.com和example2.com,并使用这些字典根据请求的服务器名称调用相应的数据库。
  2. 我真的需要在hello()函数中锁定表吗?
  3. hello()中的大部分代码是为了在每个浏览器标签页中维护一个独立的会话。我找不到仅用cookies来实现的方法,因为打开同一个网站的浏览器标签页会共享cookies池。有没有更好的方法来做到这一点?

1 个回答

14

这个错误是因为之前出现了另一个错误。看看这段代码:

>>> import psycopg2
>>> conn = psycopg2.connect('')
>>> cur = conn.cursor()
>>> cur.execute('select current _date')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
psycopg2.ProgrammingError: syntax error at or near "_date"
LINE 1: select current _date
                       ^

>>> cur.execute('select current_date')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
psycopg2.InternalError: current transaction is aborted, commands ignored until end of transaction block

>>> conn.rollback()
>>> cur.execute('select current_date')
>>> cur.fetchall()
[(datetime.date(2010, 2, 5),)]
>>> 

如果你对twisted这个库有了解,可以看看 twisted.enterprise.adbapi,那里有个例子教你怎么处理游标。简单来说,你应该总是对你的游标进行提交或者回滚:

try:
    cur.execute("...")
    cur.fetchall()
    cur.close()
    connection.commit()
except:
    connection.rollback()

撰写回答