在 psycopg2 中为连接的所有查询设置架构:设置 search_path 时获取竞争条件

Posted

技术标签:

【中文标题】在 psycopg2 中为连接的所有查询设置架构:设置 search_path 时获取竞争条件【英文标题】:Setting schema for all queries of a connection in psycopg2: Getting race condition when setting search_path 【发布时间】:2015-12-25 01:46:09 【问题描述】:

我们的系统在 Ubuntu、python 3.4、postgres 9.4.x 和 psycopg2 上运行。

我们(将来会)使用模式在 devtestprod 环境之间进行拆分。我创建了一个方便的方法来创建与我们的数据库的连接。它使用 json 连接配置文件来创建连接字符串。我想将连接配置为使用返回的连接对所有后续查询使用特定模式。我不希望我的查询具有硬编码模式,因为我们应该能够根据我们是处于开发、测试还是生产阶段/环境轻松地在它们之间切换。

目前方便的方法如下:

def connect(conn_config_file = 'Commons/config/conn_commons.json'):
    with open(conn_config_file) as config_file:    
        conn_config = json.load(config_file)

    conn = psycopg2.connect(
        "dbname='" + conn_config['dbname'] + "' " +
        "user='" + conn_config['user'] + "' " +
        "host='" + conn_config['host'] + "' " +
        "password='" + conn_config['password'] + "' " +
        "port=" + conn_config['port'] + " "
    )
    cur = conn.cursor()
    cur.execute("SET search_path TO " + conn_config['schema'])

    return conn

只要您给它时间执行 set search_path 查询,它就可以正常工作。不幸的是,如果我执行以下查询的速度太快,则会在未设置 search_path 的情况下发生竞争条件。我试图通过在return conn 之前执行conn.commit() 来强制执行,但是,这会将search_path 重置为默认模式postgres,这样它就不会使用prod。最好在数据库或应用程序层提出建议,但是,我知道我们可能也可以在操作系统级别解决此问题,也欢迎任何有关该方向的建议。

一个示例 json 配置文件如下所示:


    "dbname": "thedatabase",
    "user": "theuser",
    "host": "localhost",
    "password": "theusers_secret_password",
    "port": "6432",
    "schema": "prod"

非常感谢任何建议。

【问题讨论】:

也许在返回之前等待一秒钟? import time; time.sleep(1) 我不敢相信我正在这样做,但我会去寻求你的答案。如果您想要积分,您可以将其发布为答案,我会接受。 你的意思是,它很不雅吗?是的,只是一种解决方法 你能发布一些演示竞态条件的代码吗?鉴于您只有一个连接并且查询是按顺序执行的,因此您发布的代码中不应该存在竞争条件。如果我复制/粘贴您的代码,硬编码我的本地 Postgres 实例的连接参数,然后添加查询 SHOW search_pathSET 的结果是可见的。 不可能在单个连接中获得竞争条件。顾名思义,它至少需要两个竞争对手。 【参考方案1】:

我认为一个更好的主意是使用类似 DatabaseCursor 的返回游标来使用“SET search_path...”而不是连接来执行查询。 好吧,我的意思是这样的:

class DatabaseCursor(object):

    def __init__(self, conn_config_file):
        with open(conn_config_file) as config_file:     
            self.conn_config = json.load(config_file)

    def __enter__(self):
        self.conn = psycopg2.connect(
            "dbname='" + self.conn_config['dbname'] + "' " + 
            "user='" + self.conn_config['user'] + "' " + 
            "host='" + self.conn_config['host'] + "' " + 
            "password='" + self.conn_config['password'] + "' " + 
            "port=" + self.conn_config['port'] + " " 
        )   
        self.cur = self.conn.cursor()
        self.cur.execute("SET search_path TO " + self.conn_config['schema'])

        return self.cur

    def __exit__(self, exc_type, exc_val, exc_tb):
        # some logic to commit/rollback
        self.conn.close()

with DatabaseCursor('Commons/config/conn_commons.json') as cur:
    cur.execute("...")

【讨论】:

【参考方案2】:

我认为更优雅的解决方案是在connect()options 参数中设置search_path,如下所示:

def connect(conn_config_file = 'Commons/config/conn_commons.json'):
    with open(conn_config_file) as config_file:    
        conn_config = json.load(config_file)

    schema = conn_config['schema']
    conn = psycopg2.connect(
        dbname=conn_config['dbname'],
        user=conn_config['user'],
        host=conn_config['host'],
        password=conn_config['password'],
        port=conn_config['port'],
        options=f'-c search_path=schema',
    )
    return conn

当然,您可以使用“选项”作为连接字符串的一部分。但是使用关键字参数可以避免字符串连接的所有麻烦。

我在psycopg2 feature request 中找到了这个解决方案。至于“options”参数本身,提到了here。

【讨论】:

这在 Ubuntu 上对我不起作用,但 f"--search_path=schema" 起作用。 您收到了什么错误信息?我认为 psycopg 委托给 Postgres 的 C 库,在 ubuntu 上,您可以通过安装 libpq-dev 或 postgresql(可能带有一些后缀)来获得该库。但据我所知,libpq 也被封装在 psycopg2 的***中。您可以尝试在干净的 venv 中安装 psycopg2 并说出您得到的结果吗?是你自己编译的源码分发,还是***? 次要添加(对我很有用):如果是asyncpg,这应该通过server_settings='search_path': schema) 传递给连接或池创建函数。

以上是关于在 psycopg2 中为连接的所有查询设置架构:设置 search_path 时获取竞争条件的主要内容,如果未能解决你的问题,请参考以下文章

psycopg2 与服务器断开连接

Psycopg2 db连接在丢失的网络连接上挂起

使用 psycopg2 执行许多 SELECT 查询

如何在 hsql 中为另一个架构所有者创建表

Pyhton psycopg2的计划查询

中断键时使用 psycopg2 终止未完成的 SQL 提交查询