针对数据库性能的应用程序设计

不要在执行 SQLStatement 对象后更改其 text 属性。针对每个 SQL 语句使用一个 SQLStatement 实例并使用语句参数提供不同值。

在执行任何 SQL 语句之前,运行时会准备(编译)该语句,确定在内部执行的步骤并执行语句。在以前未执行的 SQLStatement 实例上调用 SQLStatement.execute() 时,在执行该实例之前将自动准备语句。在对 execute() 方法的后续调用中,只要 SQLStatement.text 属性未更改,仍将准备语句。因此,它的执行速度更快。

为了充分发挥重用语句的优势,如果在语句执行之间值发生了更改,请使用语句参数自定义语句。(语句参数是使用 SQLStatement.parameters 关联数组属性指定的。)与更改 SQLStatement 实例的 text 属性不同,如果更改语句参数的值,则不要求运行时再次准备语句。

重用 SQLStatement 实例时,当准备 SQLStatement 实例后,应用程序必须存储对它的引用。要保存对该实例的引用,请将变量声明为类范围的变量而不是函数范围的变量。构建应用程序是使 SQLStatement 成为类范围变量的一种好方法,这样 SQL 语句将包装在单个类中。也可以将在组合中执行的一组语句包装在单个类中。(此技术称为使用 Command 设计模式。)通过将实例定义为类的成员变量,只要包装类的实例存在于应用程序中,它们就会一直存在。至少您可以在函数外只定义一个包含 SQLStatement 实例的变量,这样,该实例将保留于内存中。例如,将 SQLStatement 实例声明为 ActionScript 类中的成员变量,或声明为 JavaScript 文件中的非函数变量。然后可以设置语句的参数值,并在要实际运行查询时调用其 execute() 方法。

使用数据库索引可以提高执行数据比较和排序的速度。

创建列索引时,数据库将存储该列数据的副本。该副本按数字或字母顺序排序。通过排序,数据库可以快速与值匹配(例如,当使用等于运算符时)并使用 ORDER BY 子句对结果数据进行排序。

数据库索引会不断更新,因此该表中的数据更改操作(插入或更新)的速度有些降低。但是,提高数据检索速度很重要。为了实现这种性能平衡,请不要只对每个表中的每列进行索引。而应使用策略定义您的索引。使用下列准则可以计划索引策略:

  • 对连接表、WHERE 子句或 ORDER BY 子句中使用的列编制索引

  • 如果这些列经常一起使用,请在单个索引中对其编制索引

  • 对于包含您检索的、按字母顺序排序的文本数据的列,请为索引指定 COLLATE NOCASE 排序规则

考虑在应用程序空闲时间期间预编译 SQL 语句。

第一次执行 SQL 语句时,速度较慢,因为要由数据库引擎准备(编译)SQL 文本。由于准备和执行语句可能要求过高,因此一种策略将预加载初始数据,然后在后台执行其他语句:

  1. 首先加载应用程序需要的数据。

  2. 当应用程序的初始启动操作完成后或在应用程序的其他“空闲”时间内,执行其他语句。

例如,假设应用程序根本不访问数据库来显示其初始屏幕。在这种情况下,请等待该屏幕显示,然后再打开数据库连接。最后,创建 SQLStatement 实例并执行可以执行的任何操作。

或者,假设应用程序在启动时就立即显示某些数据,如特定查询的结果。在该情况下,继续操作并执行该查询的 SQLStatement 实例。加载并显示初始数据后,为其他数据库操作创建 SQLStatement 实例,如有可能,则执行稍后需要的其他语句。

实际上,如果重用 SQLStatement 实例,则准备该语句需要的额外时间只是一次性成本。对总体性能可能不会有很大影响。

在一个事务中组合多个 SQL 数据更改操作。

假设要执行涉及添加或更改数据的许多 SQL 语句( INSERT UPDATE 语句)。通过在显式事务内执行所有语句,可以大大提高性能。如果不显式开始事务,则每个语句都运行在它自己的自动创建的事务中。每个事务(每个语句)完成执行后,运行时会将生成的数据写入磁盘上的数据库文件。

另一方面,应考虑显式创建事务并在该事务的上下文中执行语句时将发生的情况。运行时在内存中进行所有更改,然后在提交事务时将所有更改一次写入数据库文件。将数据写入磁盘通常是操作中最耗时的部分。因此,一次性写入磁盘而不是对每个 SQL 语句写入一次可以大大提高性能。

使用 SQLStatement 类的 execute() 方法(带有 prefetch 参数)和 next() 方法处理各部分中的大量 SELECT 查询结果。

假设要执行一个检索大型结果集的 SQL 语句。然后,应用程序将循环处理每行数据。例如,它将设置数据的格式或从其中创建对象。处理这些数据需要很长时间,这可能导致呈现问题,例如屏幕冻结或没有响应。如在 异步操作 中所述,一种解决方案会将工作分为几个区块来执行。SQL 数据库 API 简化了拆分数据处理操作。

SQLStatement 类的 execute() 方法包含一个可选参数 prefetch (第一个参数)。如果您提供一个值,它指定在执行完成时数据库返回的最大结果行数:

dbStatement.addEventListener(SQLEvent.RESULT, resultHandler); 
dbStatement.execute(100); // 100 rows maximum returned in the first set

返回第一个结果数据集后,您可以调用 next() 方法继续执行该语句并检索另一组结果行。与 execute() 方法类似, next() 方法接受 prefetch 参数以指定要返回的最大行数:

// This method is called when the execute() or next() method completes 
function resultHandler(event:SQLEvent):void 
{ 
    var result:SQLResult = dbStatement.getResult(); 
    if (result != null) 
    { 
        var numRows:int = result.data.length; 
        for (var i:int = 0; i < numRows; i++) 
        { 
            // Process the result data 
        } 
         
        if (!result.complete) 
        { 
            dbStatement.next(100); 
        } 
    } 
}

您可以继续调用 next() 方法,直到加载完所有数据。如前面的列表中所示,您可以确定何时加载完所有数据。检查每次 execute() next() 方法完成时创建的 SQLResult 对象的 complete 属性。

注: 使用 prefetch 参数和 next() 方法拆分结果数据的处理。不要使用此参数和方法将查询结果限制为其部分结果集。如果您只希望在语句的结果集中检索行的子集,请使用 SELECT 语句的 LIMIT 子句。如果结果集很大,仍可以使用 prefetch 参数和 next() 方法拆分结果的处理。
考虑使用共用一个数据库的多个异步 SQLConnection 对象来同时执行多个语句。

当使用 openAsync() 方法将 SQLConnection 对象连接到数据库后,该对象在后台(而不是在主运行时执行线程中)运行。此外,每个 SQLConnection 都在自己的后台线程中运行。通过使用多个 SQLConnection 对象,您可以同时有效地运行多个 SQL 语句。

此方法也有潜在的缺点。最重要的是,每个其他 SQLStatement 对象都需要占用额外的内存。此外,同时执行还将导致处理器的任务增多,特别是对于只有一个 CPU 或 CPU 核心的计算机。由于存在这些问题,因此不建议对移动设备使用此方法。

还有一个问题是可能丢失重用 SQLStatement 对象的潜在优势,因为 SQLStatement 对象链接到单个 SQLConnection 对象。因此,如果 SQLStatement 对象的关联 SQLConnection 对象正在使用中,则无法重用该对象。

如果选择使用连接到一个数据库的多个 SQLConnection 对象,请记住每个对象在自己的事务中执行其语句。确保考虑到在更改数据的任何代码中的各个事务,例如添加、修改或删除数据。

Paul Robertson 创建了一个开放源代码库,可以帮助您结合使用多个 SQLConnection 对象的优点并在最大程度上减少潜在缺点。此库使用 SQLConnection 对象池并管理相关联的 SQLStatement 对象。这样,就可以确保重用 SQLStatement 对象,还可以使用多个 SQLConnection 对象同时执行多个语句。有关详细信息以及要下载该库,请访问 http://probertson.com/projects/air-sqlite/