【问题标题】:Why do multiple connections from the same sqlalchemy engine produce different info?为什么来自同一个 sqlalchemy 引擎的多个连接会产生不同的信息?
【发布时间】:2016-02-03 21:13:07
【问题描述】:

我正在使用 PostgreSQL 9.3 和 SQLAlchemy 1.0.11

我的代码如下所示:

import sqlalchemy as sa

engine = sa.create_engine('postgresql+psycopg2://me@myhost/mydb')
conn = engine.connect()

metadata = sa.MetaData()

# Real table has more columns
mytable = sa.Table(
    'my_temp_table', metadata,
    sa.Column('id', sa.Integer, primary_key=True),
    sa.Column('something', sa.String(200)),
    prefixes=['TEMPORARY'],
)

metadata.create_all(engine)

pg_conn = engine.raw_connection()
with pg_conn.cursor() as cursor:
    cursor.copy_expert('''COPY my_temp_table (id, something)
                          FROM STDIN WITH CSV''',
                       open('somecsvfile', 'r'))

现在一切正常了——cursor.rowcount 报告了预期的插入行数。我什至可以运行cursor.execute('SELECT count(*) FROM my_temp_table'); print(cursor.fetchone()),它会显示相同的#。问题是当我尝试从 SQLAlchemy 的连接运行查询时,例如

    result = conn.execute(sa.text('SELECT count(*) FROM my_temp_table'))

我把它放在哪里都没关系。我尝试了几个地方:

  • with 块内
  • 在 with 块之外
  • cursor.close() 之后
  • pg_conn.close() 之后

似乎没有任何效果 - 无论我从哪里运行查询,它都会出错:

sqlalchemy.exc.ProgrammingError: (psycopg2.ProgrammingError) relation "my_temp_table" does not exist

有趣的是,如果我将该代码包装在 try/except 中,那么我可以成功地在 except 块中执行 cursor.execute(...)

实际上,现在我正在写这个,似乎使用 sqlalchemy 连接 anywhere 无法看到这些表存在。

那是什么?为什么我的 SQLAlchemy 连接看不到这些表,但 postgres (engine.raw_connection()) 可以?

编辑:

更进一步的神秘 - 如果我在metadata.create_all(engine) 之后创建连接,它就可以工作!嗯,有点。

我可以从表格中进行选择,但是当我得到engine.raw_connection() 时,它在.copy_expert 上失败,因为它找不到表格。

【问题讨论】:

    标签: python postgresql sqlalchemy temp-tables


    【解决方案1】:

    首先要注意的是,临时表只对创建它们的连接可见。

    第二个是Engine 不封装单个连接;它管理一个connection pool

    最后,documentation 指出直接在Engine(在他们的示例中为engine.execute("select ..."))执行的操作将在内部获取和释放它们自己的连接。

    考虑到所有这些,很清楚您的示例中发生了什么:

    • conn = engine.connect() 从池中获取连接 #1。
    • metadata.create_all(engine) 隐式获取 Connection #2(因为从引擎的角度来看,#1 仍在“使用中”),使用它来创建表,然后将其释放回池中。
    • pg_conn = engine.raw_connection() 再次获得#2,因此通过该对象执行的COPY 仍然可以看到该表。
    • conn 仍在使用 #1,您通过此对象执行的任何操作都无法看到您的临时表。

    在你的第二种情况下:

    • metadata.create_all(engine) 隐式获取/使用/释放连接 #1。
    • conn = engine.connect() 获得 #1 并持有它。
    • pg_conn = engine.raw_connection() 获取#2,COPY 找不到临时表。

    故事的寓意:如果您正在做的事情依赖于连接状态,您最好确定您使用的是哪个连接。直接在engine 上运行命令对于独立操作来说很好,但是对于涉及临时表的任何事情,您应该获得一个连接并在每一步(包括创建表,我建议您将其更改为metadata.create_all(conn))坚持下去。

    【讨论】:

    • 不错。我什至不知道.create_all(conn) 是一件事。像魅力一样工作,而且可能没有那么神奇;)
    • @Wayne: create_all() 将接受任何 Connectable。我想这适用于大多数需要数据库连接的东西。
    • 有趣的事实——我又一次偶然发现了同样的问题。未来的我:你现在可能已经忘记了,但是你需要使用conn = engine.connect(); pg_conn = conn.connection 来获取原始连接,所以你可以先使用metadata.create_all(conn),然后再使用原始连接。
    【解决方案2】:

    嗯,这并没有回答为什么,但它是如何完成我想要的。

    而不是:

    pg_conn = engine.raw_connection()
    
    with pg_conn.cursor() as cursor:
    

    只需将其替换为:

    with conn.connection.cursor() as cursor:
    

    SQLAlchemy 连接对象exposes its underlying DBAPI connection 通过.connection 属性。并且其中涉及的任何魔法都会做正确的事情。

    【讨论】:

      猜你喜欢
      • 2023-03-11
      • 1970-01-01
      • 1970-01-01
      • 2021-09-18
      • 2017-06-12
      • 1970-01-01
      • 2021-12-21
      • 2012-12-10
      • 2020-01-28
      相关资源
      最近更新 更多