PostgreSQL - 如何从事务块外部的代码运行 VACUUM?

问题描述 投票:0回答:9
我正在将 Python 与 psycopg2 一起使用,并且在插入数千行的日常操作之后,我尝试运行完整的

VACUUM

。问题是,当我尝试在代码中运行 
VACUUM
 命令时,出现以下错误:

psycopg2.InternalError: VACUUM cannot run inside a transaction block

如何从事务块外部的代码运行它?

如果有什么不同,我有一个简单的数据库抽象类,下面显示了其中的一个子集以供上下文使用(不可运行,省略了异常处理和文档字符串,并进行了跨行调整):

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)
    
python sql postgresql psycopg2 vacuum
9个回答
83
投票
经过更多搜索,我发现了 psycopg2 连接对象的isolation_level 属性。事实证明,将其更改为

0

 将使您脱离交易区块。将上述类的真空方法更改为以下解决方案。请注意,我还将隔离级别设置回之前的水平以防万一(默认情况下似乎是
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)

本文(接近该页面的末尾)提供了在此上下文中隔离级别的简要说明。


6
投票
对于尝试了有关此问题的所有建议但没有成功的其他人,您可能会遭受与我相同的命运:我在一次

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")
    

5
投票
此外,您还可以使用以下方式获取 Vacuum 或 Analytics 给出的消息:

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

此命令打印一个列表,其中包含 Vacuum 和 Analyse 等查询的日志消息:

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 很有用 ^^


4
投票
虽然在当前版本的 postgresql 中真空已满是有问题的,但在某些大规模操作之后强制执行“真空分析”或“重新索引”可以提高性能或清理磁盘使用情况。这是 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 的访问。


2
投票
请注意,如果您使用 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()
    

1
投票
我不知道psycopg2和PostgreSQL,但只知道apsw和SQLite,所以我想我无法提供“psycopg2”帮助。

但我觉得 PostgreSQL 的工作方式可能与 SQLite 类似,它有两种操作模式:

    事务块之外:这在语义上相当于在每个 SQL 操作周围有一个事务块
  • 在交易块内,以“BEGIN TRANSACTION”标记并以“END TRANSACTION”结束
在这种情况下,问题可能出在访问层 psycopg2 内部。当它通常以隐式插入事务直到提交为止的方式运行时,可能没有“标准方法”来进行真空处理。

当然有可能,“psycopg2”有其特殊的“真空”方法,或者特殊的操作模式,其中不启动隐式事务。

当不存在这种可能性时,只有一个选项(不更改访问层;-)):

大多数数据库都有一个 shell 程序来访问数据库。该程序可以使用管道运行此 shell 程序(将真空命令输入到 shell 中),从而使用 shell 程序进行真空处理。由于真空本身是一个缓慢的操作,因此外部程序的启动将可以忽略不计。当然,实际的程序应该先提交所有未提交的工作,否则可能会出现死锁情况 - 真空必须等到最后一个事务结束。


1
投票
虽然这不是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()
    

0
投票
from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT def vacuum_analyze_table(engine, tablename: str): # A raw connection is needed, as a standard connection does not expose the `set_isolation_level` method connection = engine.raw_connection() # Because a raw connection/cursor is being used, DO NOT WRAP your query with `text(...)` as # it will throw the error: boolean value of this clause is not defined vacuum_analyze_query = f'VACUUM ANALYZE {tablename}' # There is no get_isolation_level method on a raw connection, so to get the original value, grab it directly old_isolation = connection.isolation_level # This rectifies the error: psycopg2.errors.ActiveSqlTransaction: VACUUM cannot run inside a transaction block connection.set_isolation_level(ISOLATION_LEVEL_AUTOCOMMIT) cursor = connection.cursor() cursor.execute(vacuum_analyze_query) # Restore isolation level connection.set_isolation_level(old_isolation)
    

-3
投票
不要这样做 - 您不需要完全真空。实际上,如果您运行 Postgres 的最新版本(假设 > 8.1),您甚至不需要手动运行普通 VACUUM。

© www.soinside.com 2019 - 2024. All rights reserved.