shp2pgsql 导入后无法设置 psycopg2 自动提交

问题描述 投票:0回答:3

我正在使用 shp2pgsql 将 shapefile 加载到 postGIS 数据库中,通过 psql 进行管道传输,包装在 python 子进程中,如下所示:

command = "shp2pgsql -s 4269 -a -D -W LATIN1 file.shp table | psql -h host -d db -U user"
p=subprocess.Popen(command, shell=True)
p.communicate()

效果很好,输出如下:

Loading objects...
Shapefile type: Polygon
Postgis type: MULTIPOLYGON[2]
SET
SET
BEGIN
COMMIT

没有

END
声明,但据我所知
END
COMMIT
是等效的。

然后我想为 psycopg2 连接到同一数据库设置

con.autocommit = True
。我收到以下错误:

psycopg2.ProgrammingError: autocommit cannot be used inside a transaction

为什么 psycopg2 报告交易仍在进行中?我应该有不同的方式来关闭 psql 事务吗?

如果我不运行 shp2pgsql 子进程命令,

con.autocommit
会成功执行。默认情况下,shp2pgsql 是否在某处保留打开的事务? (http://www.bostongis.com/pgsql2shp_shp2pgsql_quickguide.bqg不建议这样做)

pg_locks
中不存在表明事务已停止/空闲的相关条目。我没有在 shp2pgsql 函数中使用 psycopg2 连接对象。而且,如果我重新创建一个新的连接对象

con = psycopg2.connect(host=db_host, user=db_user, password=db_pass, database=db_name)

在 shp2pgsql 函数之后,

con.autocommit=True
工作正常。

编辑:我当然可以在所有 shp2pgsql 导入完成后简单地创建 psycopg2 连接对象,但这在我的代码中并不理想,我宁愿了解发生了什么。

Edit2:在打开 psycopg2 连接后立即设置

con.autocommit=True
(而不是稍后设置)可以绕过此错误。

编辑3:添加MWE

import psycopg2
import os
import subprocess
from glob import glob

def vacuum(con, table=""):
    autocommit_orig = con.autocommit
    con.autocommit = True
    with con.cursor() as cur:
        cur.execute("VACUUM ANALYZE {};".format(table))
    con.autocommit = autocommit_orig

def read_shapefile(path, tablename, srid="4269"):
    command = "shp2pgsql -s {} -a -D -W LATIN1 {} {} | psql -h {} -d {} -U {}".format(srid, path, tablename, host, dbname, user)
    p=subprocess.Popen(command, shell=True)
    p.communicate()

def load_data(con, datapath):
    dir = os.path.join(datapath,dataname)
    shapefiles = glob(os.path.join(dir,"*.shp"))

    for shapefile in shapefiles:
        read_shapefile(shapefile, tablename)

if __name__ == "__main__":
    con = psycopg2.connect(host=db_host, user=db_user, password=db_pass, database=db_name)
    load_data(con, datapath)
    vacuum(con, tablename)
python postgresql psycopg2 psql
3个回答
3
投票

我看不到这是在哪里发生的,但是根据 thisthisthis,第一次将命令发送到数据库时就开始了事务。

交易是针对每个连接的,因此

psql
不应该让您绊倒。

遵循 此建议,我的建议是您在代码中的

con.rollback()
之前添加
con.autocommit=True
。这将结束以某种方式开始的隐式事务。如果您仍然拥有所需的所有数据,则会发出
SELECT
命令或类似的只读指令。

如果将

con.rollback()
con.autocommit=True
向后移动,它将允许您隔离事务开始的位置,而无需重构代码。

这是一个猜测,但也许当

psql
更改数据库状态时 psycopg2 会在那时开始事务?我还没有找到支持这个假设的文档。


3
投票

只是添加一些我自己刚才遇到类似问题时发现的额外信息......

我正在尝试这样做:

conn = psycopg2.connect(DB_URL)
conn.autocommit = True
with conn, conn.cursor() as cur:
    cur.execute("""
    CREATE DATABASE mydb;
    GRANT ALL PRIVILEGES ON DATABASE mydb TO myuser;
    """)

但是我不断得到:

psycopg2.InternalError: CREATE DATABASE cannot run inside a transaction block

我尝试了各种方法:

conn = psycopg2.connect(DB_URL)
conn.autocommit = True
conn.set_session(autocommit=True)
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)

...他们仍然给出了错误。

事实证明问题出在试图同时执行两条语句。

这有效:

conn = psycopg2.connect(DB_URL)
conn.autocommit = True
with conn, conn.cursor() as cur:
    cur.execute("CREATE DATABASE mydb")
    cur.execute("GRANT ALL PRIVILEGES ON DATABASE mydb TO myuser")

0
投票

通过在 conn 连接器上使用“with”,它会创建一个隐式事务。

只是不要使用“with”作为“conn”连接器。

您可以使用“with”创建一个上下文管理器,该管理器将为您处理事务的 conn.commit() 和 conn.rollback(),但在这种情况下您不需要上下文管理器,因为您使用 autocommit 指示= True,您不想为此 SQL 查询使用事务。

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