SQLAlchemy ORM性能测试

Flaskz封装了SQLAlchemy ORM以操作数据库,为了适配不同的使用场景,对性能进行了测试,重点测试了relationship的lazy属性对于性能的影响,以供大家参考。

因为使用的是个人电脑+VPN远程连接进行的测试,可能数据有所波动和差异。

测试环境

  • 网络 - VPN远程连接到公司内网中的MySql
  • 数据库 - MySql
  • PC配置 - Macbook Pro / 2.6 GHz 6-Core Intel Core i7 / 16 GB 2667 MHz DDR4

测试数据

数据库结构&数据量

数据模型代码

                
                    from datetime import datetime
                    from flaskz.models import ModelBase, ModelMixin
                    from sqlalchemy import Column, Integer, String, DateTime, ForeignKey
                    from sqlalchemy.orm import relationship


                    class PerfTestModel(ModelBase, ModelMixin):  # 主表
                        __tablename__ = 'perf-tests'
                        id = Column(Integer, primary_key=True, autoincrement=True)  # primary key
                        f1 = Column(String(255))
                        f2 = Column(Integer)
                        f3 = Column(DateTime(), default=datetime.now, onupdate=datetime.now)
                        f4 = Column(String(255))
                        f5 = Column(String(255))
                        f6 = Column(String(255))
                        subs1 = relationship('PerfTestSubModel1', uselist=False, cascade='all,delete-orphan', lazy='joined')  # 1:1
                        subs2 = relationship('PerfTestSubModel2', cascade='all,delete-orphan', lazy='joined')  # 1:N


                    class PerfTestSubModel1(ModelBase, ModelMixin):  # 从表1
                        __tablename__ = 'perf-test-subs-1'
                        id = Column(Integer, primary_key=True, autoincrement=True)  # primary key
                        perf_test_id = Column(Integer, ForeignKey('perf-tests.id', ondelete='CASCADE'), nullable=False)  # 外键
                        a1 = Column(String(255))
                        a2 = Column(Integer)
                        a3 = Column(DateTime(), default=datetime.now, onupdate=datetime.now)
                        a4 = Column(String(255))
                        a5 = Column(String(255))
                        a6 = Column(String(255))


                    class PerfTestSubModel2(ModelBase, ModelMixin):  # 从表2
                        __tablename__ = 'perf-test-subs-2'
                        id = Column(Integer, primary_key=True, autoincrement=True)  # primary key
                        perf_test_id = Column(Integer, ForeignKey('perf-tests.id', ondelete='CASCADE'), nullable=False)  # 外键
                        b1 = Column(String(255))
                        b2 = Column(Integer)
                        b3 = Column(DateTime(), default=datetime.now, onupdate=datetime.now)
                        b4 = Column(String(255))
                        b5 = Column(String(255))
                        b6 = Column(String(255))

                
            

测试结果

操作 结果包含relationship lazy=joined 查询时间 总时间(查询+to_dict) 备注
全量查询 6秒 8.7秒 一次连接查询
1秒 8分13秒 to_dict时循环20000次查询relationship
6秒 7.6秒 一次连接查询
2.7秒 4.3秒 一次查询
条件查询
(搜索/过滤/排序)
(返回100条数据)
0.18秒 0.19秒
0.15秒 6秒 to_dict时循环查询relationship,循环次数=数量*2
0.19秒 0.20秒
0.11秒 0.12秒
批量添加 - - 15分 循环执行了40000次插入
- - 1.7秒 一次插入
批量删除 - - - 0.6秒

测试总结

  1. lazy = joined连接查询会导致查询时间变长
    • 如果输出结果需要relationship数据,会大大减少查询次数
    • 如果输出结果不需要relationship数据,会有性能损耗浪费
  2. 如果不清楚是否需要relationship数据或只是偶尔需要,请不要设置lazy=joined,否则可能因为连接查询带来性能损耗
  3. 如果结果需要relationship数据,设置lazy=joined可以提高性能,请注意, 如果没有lazy=joined可能会因为循环查询relationship数据导致严重的性能问题
  4. lazy != joined但是结果中又需要relationship数据时,可以通过先全部加载,然后append的方式进行,性能和lazy=joined的方式基本相同
  5. 如果总数据量/分页数据量较少时,lazy=joined对性能影响不大,但是可以大大减少代码和逻辑
  6. 如果添加数据量较大,不建议通过relationship级连插入,非常的慢,可以考虑分类批量插入的方式

外连接查询

如果没有设置relationship或者relationship的lazy属性是noload,但是输出结果中又希望包含关联表的信息,可以考虑使用外连接的查询方式。

将数据读取出来以后,再对结果进行拼接的方式,示例如下

                
                    session = get_db_session()
                    query = session.query(PerfTestModel, PerfTestSubModel1, PerfTestSubModel2)
                    query = query.outerjoin(PerfTestSubModel1).outerjoin(PerfTestSubModel2)     # 外连接查询
                    id_map = {}
                    for item in query.all():    # 每条结果数据是包含三个数据模型对象的元组
                        model = item[0]
                        id = model.id
                        model_value = id_map.get(id)
                        if model_value is None:     # 数据拼接
                            model_value = id_map[id] = model.to_dict()
                            model_value['subs1'] = item[1].to_dict()
                            model_value['subs2'] = []
                            result.append(model_value)
                        model_value.get('subs2').append(item[2].to_dict())
                    print(result)