[英]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
( ischema
是ischema
的缩写,此代码正在该表上运行选择。)
我没有看到一个好的,简单的方法来解决这个问题。 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.