为什么将字符串分配给 DATETIME 列会导致提交速度如此之慢?合理吗?
Why assign a string to DATETIME column would result in so much slower commit speed? Is it reasonable?
最近几天我一直在开发一个非常简单的网页,我决定使用 SQLAlchemy 和 Flask 作为后端堆栈。
Table DDL:
create table if not exists testdb.table_datetime
(
id int auto_increment
primary key,
col1 varchar(50) null,
col2 varchar(50) null,
col3 varchar(50) null,
col4 varchar(50) null,
col5 varchar(50) null,
col6 varchar(50) null,
local_modified datetime null
)
collate=utf8mb4_bin;
我的 ORM 模型:
class DemoClass(Base):
__tablename__ = 'table_datetime'
local_modified = Column(DATETIME)
# __tablename__ = 'table_varchar'
# local_modified = Column(String)
id = Column(Integer, primary_key=True)
col1 = Column(String)
col2 = Column(String)
col3 = Column(String)
col4 = Column(String)
col5 = Column(String)
col6 = Column(String)
代码:
with Session() as session:
db_rows = session.query(DemoClass).all()
for item in data:
for db_row in db_rows:
if item['id'] == db_row.id:
db_row.col1 = item['col1']
db_row.col2 = item['col2']
db_row.col3 = item['col3']
db_row.col4 = item['col4']
db_row.col5 = item['col5']
db_row.col6 = item['col6']
# db_row.local_modified = item['local_modified'] # Very slow, 6-8s for 500 rows data
db_row.local_modified = datetime.strptime(item['local_modified'], '%Y-%m-%d %H:%M:%S') # Fast, <1s for 500 rows data
break
session.commit()
我发现与 datetime.strptime(2021-10-02 11:12:34', '%Y-%m-%d %H:%M:%S')
相比,将字符串 '2021-10-02 11:12:34'
分配给 ORM DATETIME
会慢得多。
我的测试环境:
Flask==2.0.1
PyMySQL==1.0.2
MySQL==1.0.2
SQLAlchemy==1.4.25
Python 3.6.5 Windows
MySQL remote server, Server version 5.7.33-0ubuntu0.18.04.1
结果是,对于要更新的 500 行,分配一个日期时间字符串需要 6 到 10 秒,而先将日期时间字符串转换为 datetime
然后分配需要不到 1 秒。
完整的测试代码和设置在这里:https://github.com/ajfg93/sqlalchemy-demo/tree/datetime_slow
我的问题是,性能下降是否合理(不到 1 秒对 6 到 10 秒)?
我想如果我传递的是日期时间字符串而不是 datetime
对象,内部代码(SQLAlchemy 或 MySQL,我不知道谁会做这项工作)可能会尝试猜测字符串格式并得到它认为正确的 datetime
对象?但真的那么慢吗?还有其他原因吗?
如果我不测试代码,我可能永远不会知道它 db_row.local_modified = item['local_modified']
减慢了整个处理流程。我的意思是,我更希望 SQLAlchemy 引发异常,告诉我数据类型不匹配,而不是导致这种巨大的性能下降。
使用这个例子附加了不同之处,因为在日期时间的情况下,SQLAlchemy 发现对象没有变化,也没有更新发送到数据库,这可以通过锁定引擎打印的日志来验证.
如果您将 + timedelta(1)
添加到日期,您应该会看到相似的时间。
关于字符串的操作,SQLAlchemy 不解析它们,它们按原样传递给 dbpi。您可以通过锁定日志来验证它,您将在其中看到传递给数据库的参数。
关于耗时,对我来说所有情况下都不到1s
我重写了一些代码,保证每次post的数据都不一样,从而保证SQLAlchemy会更新。
然后我用本地MySQL再次测试了它并且所有响应时间<1s。
所以真正的问题原来是我之前连接到远程MySQL(public网络)。没想到整个处理过程中网络IO贡献了这么大的开销
仅供参考,基准测试结果:
bulk_update_mappings
:~80 毫秒
- 方法 2:~150 毫秒并且传递日期时间字符串不会增加太多开销
session.merge
: 500ms~,最慢,符合预期。
最近几天我一直在开发一个非常简单的网页,我决定使用 SQLAlchemy 和 Flask 作为后端堆栈。
Table DDL:
create table if not exists testdb.table_datetime
(
id int auto_increment
primary key,
col1 varchar(50) null,
col2 varchar(50) null,
col3 varchar(50) null,
col4 varchar(50) null,
col5 varchar(50) null,
col6 varchar(50) null,
local_modified datetime null
)
collate=utf8mb4_bin;
我的 ORM 模型:
class DemoClass(Base):
__tablename__ = 'table_datetime'
local_modified = Column(DATETIME)
# __tablename__ = 'table_varchar'
# local_modified = Column(String)
id = Column(Integer, primary_key=True)
col1 = Column(String)
col2 = Column(String)
col3 = Column(String)
col4 = Column(String)
col5 = Column(String)
col6 = Column(String)
代码:
with Session() as session:
db_rows = session.query(DemoClass).all()
for item in data:
for db_row in db_rows:
if item['id'] == db_row.id:
db_row.col1 = item['col1']
db_row.col2 = item['col2']
db_row.col3 = item['col3']
db_row.col4 = item['col4']
db_row.col5 = item['col5']
db_row.col6 = item['col6']
# db_row.local_modified = item['local_modified'] # Very slow, 6-8s for 500 rows data
db_row.local_modified = datetime.strptime(item['local_modified'], '%Y-%m-%d %H:%M:%S') # Fast, <1s for 500 rows data
break
session.commit()
我发现与 datetime.strptime(2021-10-02 11:12:34', '%Y-%m-%d %H:%M:%S')
相比,将字符串 '2021-10-02 11:12:34'
分配给 ORM DATETIME
会慢得多。
我的测试环境:
Flask==2.0.1
PyMySQL==1.0.2
MySQL==1.0.2
SQLAlchemy==1.4.25
Python 3.6.5 Windows
MySQL remote server, Server version 5.7.33-0ubuntu0.18.04.1
结果是,对于要更新的 500 行,分配一个日期时间字符串需要 6 到 10 秒,而先将日期时间字符串转换为 datetime
然后分配需要不到 1 秒。
完整的测试代码和设置在这里:https://github.com/ajfg93/sqlalchemy-demo/tree/datetime_slow
我的问题是,性能下降是否合理(不到 1 秒对 6 到 10 秒)?
我想如果我传递的是日期时间字符串而不是 datetime
对象,内部代码(SQLAlchemy 或 MySQL,我不知道谁会做这项工作)可能会尝试猜测字符串格式并得到它认为正确的 datetime
对象?但真的那么慢吗?还有其他原因吗?
如果我不测试代码,我可能永远不会知道它 db_row.local_modified = item['local_modified']
减慢了整个处理流程。我的意思是,我更希望 SQLAlchemy 引发异常,告诉我数据类型不匹配,而不是导致这种巨大的性能下降。
使用这个例子附加了不同之处,因为在日期时间的情况下,SQLAlchemy 发现对象没有变化,也没有更新发送到数据库,这可以通过锁定引擎打印的日志来验证.
如果您将 + timedelta(1)
添加到日期,您应该会看到相似的时间。
关于字符串的操作,SQLAlchemy 不解析它们,它们按原样传递给 dbpi。您可以通过锁定日志来验证它,您将在其中看到传递给数据库的参数。
关于耗时,对我来说所有情况下都不到1s
我重写了一些代码,保证每次post的数据都不一样,从而保证SQLAlchemy会更新。
然后我用本地MySQL再次测试了它并且所有响应时间<1s。
所以真正的问题原来是我之前连接到远程MySQL(public网络)。没想到整个处理过程中网络IO贡献了这么大的开销
仅供参考,基准测试结果:
bulk_update_mappings
:~80 毫秒- 方法 2:~150 毫秒并且传递日期时间字符串不会增加太多开销
session.merge
: 500ms~,最慢,符合预期。