繁体   English   中英

SQLAlchemy 在插入期间挂起,同时查询 [INFORMATION_SCHEMA]。[TABLES]

[英]SQLAlchemy hangs during insert while querying [INFORMATION_SCHEMA].[TABLES]

我有一个 Python 进程,它使用 SQLAlchemy 将一些数据插入 MS SQL 服务器数据库。 当 Python 进程运行时,它在插入期间挂起。 我打开了 SQLAlchemy 日志记录以获取更多信息。 我发现它在 SQLAlchemy 似乎正在请求有关整个数据库的表模式信息的地方挂起:

2020-10-30 08:12:07 [11444:6368] sqlalchemy.engine.base.Engine._execute_context(base.py:1235) INFO: SELECT [INFORMATION_SCHEMA].[TABLES].[TABLE_NAME] 
FROM [INFORMATION_SCHEMA].[TABLES] 
WHERE [INFORMATION_SCHEMA].[TABLES].[TABLE_SCHEMA] = CAST(? AS NVARCHAR(max)) AND [INFORMATION_SCHEMA].[TABLES].[TABLE_TYPE] = CAST(? AS NVARCHAR(max)) ORDER BY [INFORMATION_SCHEMA].[TABLES].[TABLE_NAME]
2020-10-30 08:12:07 [11444:6368] sqlalchemy.engine.base.Engine._execute_context(base.py:1240) INFO: ('dbo', 'BASE TABLE')

此时我在数据库中还有其他“东西”,包括一些开放的事务,我的猜测是,无论出于何种原因,查询[INFORMATION_SCHEMA].[TABLES]都会以某种方式创建一些死锁或阻塞。

我也读过( 这里[INFORMATION_SCHEMA].[TABLES]是一种不会导致死锁的视图,这与我对导致此问题的原因的猜测相矛盾。

我的问题是:我可以更改 SQLAlchemy 的配置/设置,使其首先不进行此查询吗?

更新 1 :插入的 Python 代码如下:

with sqlalchemy.create_engine("mssql+pyodbc:///?odbc_connect=%s" % params).connect() as connection:
    # df is a Pandas DataFrame
    df.to_sql(name=my_table, con=connection, if_exists='append', index=False)

请注意,当我在一天中没有进行其他数据库事务的其他时间运行 Python 脚本时,代码可以正常工作。 在这些情况下,日志会像这样立即继续,列出数据库中的所有表:

2020-10-30 08:13:03 [11444:6368] sqlalchemy.engine.base.Engine._execute_context(base.py:1235) INFO: SELECT [INFORMATION_SCHEMA].[TABLES].[TABLE_NAME] 
FROM [INFORMATION_SCHEMA].[TABLES] 
WHERE [INFORMATION_SCHEMA].[TABLES].[TABLE_SCHEMA] = CAST(? AS NVARCHAR(max)) AND [INFORMATION_SCHEMA].[TABLES].[TABLE_TYPE] = CAST(? AS NVARCHAR(max)) ORDER BY [INFORMATION_SCHEMA].[TABLES].[TABLE_NAME]
2020-10-30 08:13:03 [11444:6368] sqlalchemy.engine.base.Engine._execute_context(base.py:1240) INFO: ('dbo', 'BASE TABLE')
2020-10-30 08:13:03 [11444:6368] sqlalchemy.engine.base.Engine._init_metadata(result.py:810) DEBUG: Col ('TABLE_NAME',)
2020-10-30 08:13:03 [11444:6368] sqlalchemy.engine.base.Engine.process_rows(result.py:1260) DEBUG: Row ('t_table1',)
2020-10-30 08:13:03 [11444:6368] sqlalchemy.engine.base.Engine.process_rows(result.py:1260) DEBUG: Row ('t_table2',)
...

更新 2:显然,当在打开的事务中创建表或其他 object 且尚未提交时,查询[INFORMATION_SCHEMA].[TABLES]将被阻止( 来源)。 是否有人熟悉 SQLAlchemy 的内部结构来建议如何防止它首先进行此查询?

UPDATE 3 : After posting this issue on the SQLAlchemy github ( issue link) the SQLAlchemy devs confirmed that the query of [INFORMATION_SCHEMA].[TABLES] is in fact being caused by the Pandas function to_sql() .

所以,我的新问题是有人知道如何在 Pandas to_sql() function 中禁用此行为吗? 我查看了文档,找不到任何似乎有帮助的东西。

我对 SQLAlchemy 不是很熟悉,但我可以告诉你这个问题的 Pandas 方面。

如果表不存在,Pandas 会自动创建一个新表。 它判断表是否存在的方法是在 SQL Alchemy 中调用has_table() has_table()工作方式是查询信息模式。 (至少,它在 MySQL 和 MSSQL 中是这样工作的。)

实施细则

这是我在 Pandas 和 SQLAlchemy 中找到的跟踪逻辑的内容。 我们从 pandas/io/sql.py 开始,在to_sql()

        table = SQLTable(
            name,
            self,
            frame=frame,
            index=index,
            if_exists=if_exists,
            index_label=index_label,
            schema=schema,
            dtype=dtype,
        )
        table.create()

SQLTable.create() 在这里定义:

class SQLTable(PandasObject):
    [...]
    def create(self):
        if self.exists():
            if self.if_exists == "fail":
                raise ValueError(f"Table '{self.name}' already exists.")
            elif self.if_exists == "replace":
                self.pd_sql.drop_table(self.name, self.schema)
                self._execute_create()
            elif self.if_exists == "append":
                pass
            else:
                raise ValueError(f"'{self.if_exists}' is not valid for if_exists")
        else:
            self._execute_create()

请注意,它无条件地调用exists() SQLTable.exists()里面,你会发现:

    def exists(self):
        return self.pd_sql.has_table(self.name, self.schema)

这最终在 SQLAlchemy 中调用has_table()https : has_table()

对于 MSSQL,这是在 SQLAlchemy 中的 sqlalchemy/dialects/mssql/base.py 中实现的:

    @_db_plus_owner
    def has_table(self, connection, tablename, dbname, owner, schema):
        if tablename.startswith("#"):  # temporary table
            [...]
        else:
            tables = ischema.tables

            s = sql.select(tables.c.table_name).where(
                sql.and_(
                    tables.c.table_type == "BASE TABLE",
                    tables.c.table_name == tablename,
                )
            )

            if owner:
                s = s.where(tables.c.table_schema == owner)

            c = connection.execute(s)

            return c.first() is not None

ischemaischema的缩写,此代码正在该表上运行选择。)

如何解决这个问题

我没有看到一个好的,简单的方法来解决这个问题。 Pandas 假设has_table()是一个廉价的操作。 MSSQL 不遵循该假设。 没有什么事if_exists设置为,大熊猫将调用has_table()期间to_sql()

不过,我可以想到一种很酷的方法来做到这一点。 如果您要对pandas.io.sql.SQLTable.create()进行猴子补丁,使其成为空操作,那么您可以诱使 Pandas 认为该表已经存在。 这样做的缺点是 Pandas 不会自动创建表。

在调用 to_sql/ 之前执行设置事务隔离级别读取未提交

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM