11.8 通过创建索引提高性能

    提高一个关系数据库(例如SQLite)的途径之一是加快连接操作的执行。我们不希望SQLite对整表进行读取来查询匹配的行。通过在一个指定的列上创建索引,SQLite会对索引进行检测并只会从表中读取相关的行。

    当我们定义了一个在查询中会使用的列时,就应该考虑为这个列创建索引。这也是在SQLAlchemy中的一种简单的处理方式,我们只需在类的属性中添加注释index=True

    我们可以对Post表做小幅度的改动,例如,可以使用如下代码来添加索引。

    class Post(Base):
      tablename = "POST"
      id = Column(Integer, primary_key=True)
      title = Column(String, index=True)
      date = Column(DateTime, index=True)
      blog_id = Column(Integer, ForeignKey('BLOG.id'), index=True)

    我们为标题和日期添加了索引,当查询匹配项是标题或日期时,这个对文章的查询就会加速执行。然而并不能一定保证在性能上会得到一定提升。关系数据库包含了一系列因素。在实际场景中,对是否有索引的两种情况下分别做测试是重要的。

    同样的,为blog_id添加索引,可能会加速BlogPost表中行的连接操作。而在数据库引擎中使用了某种特殊算法,导致创建的索引并没有起到效果也是有可能的。

    索引会带来存储和计算的负载。如果一个索引很少使用,那么创建和维护的代价就会成为一个问题,而非一种解决方案。另外,一些特殊的索引可以对性能带来显著的提高。在任何情况下,我们并没有机会来直接操作数据库所使用的算法,我们所能做的就是创建索引然后评估它对性能带来的影响。

    模型演化

    当使用一种数据库时,必须解决模型演化的问题。对象中包括动态的状态和静态的类定义,存储动态的状态很方便。类定义是持久化数据模式的一部分,我们也具备了SQL模型的映射,类和SQL模型都是绝对静态的。

    如果改变一个类定义,那么如何从数据库中取出对象?如果必须改变数据库,如何对 Python 映射的实现进行改进使之可以仍然有效?一个良好的设计通常包括几种技术的结合。

    对方法函数和Python类中特性的改变并不会影响到SQL行的映射,可能只会带来很小的改动,因为数据库中的表与改动后的类定义仍是兼容的。一个新的软件发布会有一个新的次版本号。

    改变 Python 类中的特性的同时并不必要改动持久化的对象状态。在将数据库中的数值类型转换到Python对象的过程中,SQL是很灵活的。一个ORM层可以带来灵活性。在一些情况下,可以对一些类或数据库做一些改变,这个过程只是对次版本的升级,因为已有的 SQL 模型仍然会与新的类定义兼容。例如,可以将 SQL 表中的整型改为字符串,由于SQL和ORM之间的转换,这个操作不会带来任何中断。

    对SQL表定义的改变显然会改变持久化对象。当数据库中已有的行与新的类定义不再兼容时,意味着这是一个很大的改动,这种改动不该通过修改Python类定义来完成。这种改动应该通过定义一个新的子类,然后提供一个新的工厂函数,用于完成创建新类或旧类的实例。

    当对 SQL 数据进行持久化时,模型的改变可以通过以下两种方式中的任何一种来实现。

    • 对已有的模型使用SQL中的ALTER语句。对一个SQL模型的一些改变可以是持续性的。至于哪些改变是允许的,关于这点有很多约束和限制。并不是针对所有情况都如此,对于一些小的改动而言,应该视为异常情况。
    • 创建新表,删除旧表。一般地,在SQL中对模型的改动已经很大了,需要基于旧表来创建新表,这样会对数据库结构进行大的改动。

    SQL数据库模型的改动通常需要执行一个转换脚本,这个脚本会使用旧模型对已有数据进行查询,将它转换为新数据,并使用新模型来将新数据插入到数据库中。当然,要在应用到用户正在使用的、在线的、可操作的数据库之前,一定先对备份数据库进行测试。数据库模型的改变一旦完成,旧的模型就可以删除以节省存储空间。

    这种类型的转换可以在同一个数据库中完成,使用不同的表名或不同的模型名称(对于支持命名模型的数据库而言)。如果同时保留旧数据和新数据,这样在做软件升级时就会灵活一些。对于希望提供24小时×7天服务的网站来说,这点显得尤其重要。

    在一些情况下,向数据库中添加一些只包含了一些审计信息的表是必要的,例如模型的版本标识。应用可以在创建数据库连接后,先对这个表进行查询,当检测到模型版本错误时就直接返回。