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

时间:2015-09-27 20:22:40

标签: python postgresql python-3.x schema psycopg2

我们的系统在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未设置的情况下发生竞争条件。我已尝试在conn.commit()之前执行return conn强制执行,但是,这会将search_path重置为默认架构postgres,因此它不会#&# 39; t使用,比方说,prod。在数据库或应用程序层的建议是可取的,但是,我知道我们可能也可以在操作系统级别解决这个问题,也欢迎任何有关这方面的建议。

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

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

非常感谢任何建议。

2 个答案:

答案 0 :(得分:10)

我认为更优雅的解决方案是在[Serializable()] class NodeTag : ISerializable { public NodeTag(string Subnet) { Broadcast_Address = Stuff; Network_Name = Stuff; CIDR_Value = Stuff; } public string Network_Name { get; set; } public string CIDR_Value { get; set; } public string Broadcast_Address { get; set; } //Deserializer - loads back from file data //This is an overloaded function that defines the data object when it is being reconstituted public NodeTag(SerializationInfo info, StreamingContext context) { Network_Name = (String)info.GetValue("Network_Name", typeof(string)); CIDR_Value = (String)info.GetValue("CIDR_Value", typeof(string)); Broadcast_Address = (String)info.GetValue("Broadcast_Address", typeof(string)); } //Serializer - loads into file data public void GetObjectData(SerializationInfo info, StreamingContext context) { info.AddValue("Network_Name", Network_Name); info.AddValue("CIDR_Value", CIDR_Value); info.AddValue("Broadcast_Address", Broadcast_Address); } } 的{​​{1}}参数中设置search_path,如下所示:

options

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

我在此psycopg2 feature request中找到了此解决方案。至于"选项"参数本身,它提到了here

答案 1 :(得分:6)

我认为更好的想法是让像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("...")