为什么将字符串分配给 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~,最慢,符合预期。