分类:
2008-04-24 10:43:40
SQL 的范例是,用户(或应用程序)只需告诉数据库系统要 做什么,系统自己就会得出 如何做 这件事的最佳方法。因此,SQL 是一种纯描述性语言。DB2 优化器尽力实现这个理想,通常做得也很好。但是,还存在一些特殊情况,在这些情况下,用户的干预可以改善性能。因此,我们描述一些您应该记住的用于空间查询和空间 DML 语句的概念。
处理多个行
一开始,SQL (现在也仍然)是一种面向集合的语言,而不是过程语言。这意味着同时处理一个集合中相关的行,而不是一个接一个地处理每一行。一个很好的例子就是INSERT 语句:如果需要将多个行插入到相同的表中,那么是在一条 SQL 语句中处理所有那些行,而不是触发多条语句。这一事实对于空间数据尤其有意义,因为几乎所有空间函数的实现都使得在同一条 SQL 语句中多次执行一个函数可以得到性能好处。在第一次调用函数时,函数的内部处理执行一些必要的初始化,例如设置计算所需的内存区域,随后的调用则利用这个已建立好的基础设施,直到在这条语句的作用域内最后一次调用函数时销毁这个基础设施。
所以,如果为每个要插入的行单独执行一条INSERT 语句,那么前面提到的基础设施的初始化和销毁将每执行一次就重复一次,即对于每一行都重复一次。而将这些语句组合成一条单独的语句可以避免重复的初始化和销毁步骤,从而提升总体性能。此外,同时处理多个行还可以提高性能,因为减少了应用程序(DB2 客户机)与数据库引擎之间的交互次数。
让我们通过一个简单的例子来展示上述效果,在这个例子中,我们插入 50 个不同的行。首先,像清单 12 显示的第 1 个语句那样,使用 SQL 分别插入所有的行。清单 12 中的第 2 个语句将所有 50 行组合到一个单独的插入操作中。其思想是,在一个描述性级别上,在 FROM 子句中建立一个临时表,这个表由在调用任何空间函数之前导入的数据组成。接着,扫描那个临时表中的所有行,并应用空间函数,从而构建一个新的表,最终这个表中的数据被插入 tab 表中。
清单 12. 逐行操作与多行操作的比较
-- insert just a single row INSERT INTO tab VALUES ( 1, db2gse.ST_LineString( 'linestring (11.9963970 54.9979739, 11.9947259 55.0000000)', 1003) )@ -- insert multiple rows at once INSERT INTO tab SELECT id, db2gse.ST_LineString(wkt, 1003) FROM TABLE ( VALUES ( 1, 'linestring (11.9963970 54.9979739, 11.9947259 55.0000000)' ), ( 2, 'linestring (11.9872250 55.0000000, 11.9963970 54.9979739)' ), ... ) AS t(id, wkt)@ |
应该注意的是,大部分时间花在解析和编译 SQL 语句上。如果使用了预置语句,那么两种方法之间的差距可能拉大,也可能缩小,这取决于您的应用程序、系统配置和数据。但是,不大可能出现第一种场景好于多行语句的情况。而且应该记住,构造函数ST_LineString 不执行任何复杂的空间计算;它只是在文本表示上执行一次 single-sweep 扫描,并将坐标转换成内部编码。
该函数占用的内存也很少。还应记住,预置语句可用于多行插入(例如,在静态嵌入式 SQL 应用程序中)并且多行插入可以执行多次,以利用上述优点。同样的技术还用于空间导入过程中,这就是为什么在消息文件的一开始出现像 “Using 342 rows per single INSERT statement” 这样的信息性消息的原因。在导入期间,总行数受 SQL 语句的最大可能大小、提交范围(commit scope)、被导入的总行数或所有这些因素的限制。
对INSERT 语句的这些考虑同样适用于SELECT 或UPDATE 语句。例如,DB2 Spatial Extender 存储过程ST_run_gc 对一组行进行地理编码(geocode)。如果为提交范围指定一个值,那么该过程可能不会立即处理所有受影响的行,但是其间它必须为这些行计数,并执行一个COMMIT。初级的方法是使用一个游标对表进行扫描,并为每一行执行一个定位的更新语句。然而,定位更新会碰到我们刚才讨论的一个问题,那就是必须一次又一次地初始化地理编码器。
如果地理编码不仅仅是构造一个 linestring,而是执行更复杂的功能,那么对性能的影响将会更加显著。所以,如果在表中找到一个标识列,例如主键,则可以找到一种不同的解决方案。我们使用 DB2 OLAP 函数 row_number() 将一个惟一的数值赋给每个受影响的列,然后运行一个 block-wise 搜索更新,之后执行一次COMMIT。清单 13 阐释了这一点。最里边的子查询找出所有需要更新的行;用户可能已经给出了一些条件来限制这些行。此外,每个行被赋予一个行号。下一步则根据行号过滤出那些属于将被地理编码的当前块的行。最后,最外面的UPDATE 在相同的 SQL 作用域内将那些行逐个提供给地理编码器函数。这样,地理编码器只需为一个块进行初始化,而不必为每一行进行初始化。