postgresql 如何从事务块外的代码运行VACUUM?

ma8fv8wu  于 2023-01-12  发布在  PostgreSQL
关注(0)|答案(8)|浏览(228)

我在psycopg2中使用Python,在一次插入数千行的日常操作之后,我试图运行一个完整的VACUUM,问题是当我试图在代码中运行VACUUM命令时,我得到了以下错误:

psycopg2.InternalError: VACUUM cannot run inside a transaction block

如何从事务块外部的代码运行此操作?
如果有区别的话,我有一个简单的DB抽象类,下面显示了它的一个子集作为上下文(不可运行,省略了异常处理和文档字符串,并进行了跨行调整):

class db(object):
    def __init__(dbname, host, port, user, password):
        self.conn = psycopg2.connect("dbname=%s host=%s port=%s \
                                      user=%s password=%s" \
                                      % (dbname, host, port, user, password))

        self.cursor = self.conn.cursor()

    def _doQuery(self, query):
        self.cursor.execute(query)
        self.conn.commit()

    def vacuum(self):
        query = "VACUUM FULL"
        self._doQuery(query)
i1icjdpr

i1icjdpr1#

对于其他任何一个围绕这个问题尝试了所有建议都没有成功的人来说,你可能会遭受和我一样的命运:我在一个execute()调用中有2个(或更多)SQL语句,结果Postgres自己在第一个语句(由;分隔)之后重置了所有自动提交/隔离。https://github.com/psycopg/psycopg2/issues/1201
所以不要做这样的事情:

cursor.execute("SELECT 1; VACUUM FULL")

相反,应:

cursor.execute("SELECT 1")
cursor.execute("VACUUM FULL")
gdrx4gfi

gdrx4gfi2#

此外,您还可以使用以下方法获得真空或分析给出的消息:

>> print conn.notices #conn is the connection object

此命令打印包含查询(如真空和分析)日志消息的列表:

INFO:  "usuario": processados 1 de 1 páginas, contendo 7 registros vigentes e 0 registros não vigentes; 7 registros amostrados, 7 registros totais estimados   
INFO:  analisando "public.usuario"

这对DBA很有用^^

ou6hu8tu

ou6hu8tu3#

尽管在当前版本的postgresql中vacuum full是有问题的,但是在某些大规模操作之后强制执行“vacuum analyze”或“reindex”可以提高性能,或者清理磁盘使用。这是postgresql特有的,需要清理才能对其他数据库做正确的事情。

from django.db import connection
# Much of the proxy is not defined until this is done
force_proxy = connection.cursor()
realconn=connection.connection
old_isolation_level = realconn.isolation_level
realconn.set_isolation_level(0)
cursor = realconn.cursor()
cursor.execute('VACUUM ANALYZE')
realconn.set_isolation_level(old_isolation_level)

不幸的是django提供的连接代理不提供set_isolation_level的访问权限。

vybvopom

vybvopom4#

注意如果你使用Django和South来执行迁移,你可以使用下面的代码来执行VACUUM ANALYZE

def forwards(self, orm):

    db.commit_transaction()
    db.execute("VACUUM ANALYZE <table>")

    #Optionally start another transaction to do some more work...
    db.start_transaction()
n8ghc7c1

n8ghc7c15#

我不知道psycopg 2和PostgreSQL,但只有apsw和SQLite,所以我想我不能给予一个“psycopg 2”的帮助。
但在我看来,PostgreSQL的工作原理可能与SQLite类似,它有两种操作模式:

  • 事务块外部:这在语义上等同于在每个SQL操作周围都有一个事务块
  • 在事务块内部,标记为“开始TRANSACTION”并以“END TRANSACTION”结束

在这种情况下,问题可能出在访问层psycopg 2内部。当它以隐式插入事务直到提交的方式正常运行时,可能没有“标准方法”来制造真空。
当然,“psycopg 2”有其特殊的“vacuum”方法或特殊的操作模式,在这种方法中不启动隐式事务。
当不存在这样的可能性时,保留一个单一选项(不改变接入层;- )):
大多数数据库都有一个shell程序来访问数据库。这个程序可以通过管道来运行这个shell程序(将vacuum-command输入shell),从而使用shell程序来执行vacuum。由于vacuum-command本身是一个缓慢的操作,因此外部程序的启动将被忽略。当然,实际程序应在此之前提交所有未提交的工作,否则可能会出现死锁情况-真空必须等到最后一个事务结束。

r1zk6ea1

r1zk6ea16#

虽然这不是OP的确切情况,但我也遇到了这种情况。结果表明,它与连接用作上下文时打开的隐式事务有关。例如,如果OP的代码类似于以下内容,即使使用正确的隔离级别,它也会因为不同的原因而遇到完全相同的错误:

class db(object):
    def __init__(dbname, host, port, user, password):
        self.conn = psycopg2.connect("dbname=%s host=%s port=%s \
                                      user=%s password=%s" \
                                      % (dbname, host, port, user, password))
        self.conn.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT)

    def _doQuery(self, query):
        # THE FOLLOWING LINE OPENS AN IMPLICIT TRANSACTION
        # No matter the autocommit settings, this WILL create a transaction
        with self.conn:
            with conn.cursor() as cursor:
                cursor.execute(query)
                self.conn.commit()

    def vacuum(self):
        query = "VACUUM FULL"
        self._doQuery(query)

解决方案是不使用连接作为上下文:

def _doQuery(self, query):
        # Note no "with conn:" context anymore
        with conn.cursor() as cursor:
            cursor.execute(query)
            self.conn.commit()
2wnc66cl

2wnc66cl7#

不要这样做-你不需要VACUUM FULL。实际上,如果你运行最近版本的Postgres(比如说〉8.1),你甚至不需要手动运行普通的VACUUM。

sbtkgmzw

sbtkgmzw8#

经过更多的搜索,我发现了psycopg2连接对象的isolation_level属性。原来将其更改为0会将您移出事务块。将上述类的vacuum方法更改为以下方法解决了这个问题。注意,我还将隔离级别设置回以前的值,以防万一(默认情况下似乎是1)。

def vacuum(self):
    old_isolation_level = self.conn.isolation_level
    self.conn.set_isolation_level(0)
    query = "VACUUM FULL"
    self._doQuery(query)
    self.conn.set_isolation_level(old_isolation_level)

This article(接近该页末尾)简要说明了此上下文中的隔离级别。

相关问题