在执行查询时,Mondrian有时需要决定如何进行查询,特别是如何生成SQL。一个决定是使用哪个聚合表进行查询(或者是否坚持使用事实表),另一个决定则是是否将2011年48个州和10个月的 cell request “四舍五入”为50个州和12个月的完整segment。
这些决策是由数据库中的实际数据量决定的。第一个决策是统计行数(事实表和聚合表中的行数),第二个决策是统计列的基数(“月”和“州”列中不同值的数量)。
收集统计信息是一门不完善的科学。获取信息的最直接的方法是执行一些SQL查询:
-- row count of the fact table
select count(*) from sales_fact_1997;
-- count rows in an aggregate table
select count(*) from agg_sales_product_brand_time_month;
-- cardinality of the [Customer].[State] attribute
select count(distinct state) from customer;
但是这些查询可能非常昂贵。(在许多数据库中,统计行数包括将表的每个块读取到内存中,并将每个块中的行数相加。查询列的基数涉及索引的条目扫描;或者,更糟的是如果没有索引则进行全表扫描,然后进行昂贵的排序。)
Mondrian不需要精确的值,但需要一个近似值(比如在3倍内)才能继续查询。
Mondrian有一个统计数据的缓存,因此统计数据的调用只影响“当天的第一个查询”,即Mondrian重新启动或使用新的Schema文件时。(如果你使用的是动态Schema处理器(DSP),可能每个用户都有自己的Schema。在这种情况下,每个用户都会经历自己缓慢的“一天中的第一次查询”。)
有一种机制可以防止昂贵的查询:您可以在Mondrian的Schema文件中提供预估值。在定义聚合表时,如果指定XML元素的approxRowCount属性,Mondrian将跳过行计数查询。定义级别时,如果指定XML元素(mondrian-4中的XML元素)的approxRowCount属性,mondrian将跳过基数查询。但填写这些计数很耗时,而且随着数据库的增长,这些计数可能会过时。
可以考虑以下2个功能来缓解这个问题。
自动填充approxRowCount
自动填充功能将读取Schema文件,在数据库上运行查询以统计每个事实表、聚合表和每个级别的key,并填充到Schema文件中的approxRowCount属性。它还可以进行一些健全性检查,例如维度表的主键没有任何唯一值,并在违反这些值时发出警告。
可插拔的统计信息
Mondrian需要的统计数据可能已经存在。每个数据库都有一个查询优化器,每个查询优化器都需要统计行数和列的基数等信息来做出决策。因此,您在填充数据库后运行的ANALYZE TABLE(或等效命令)可能会计算出这些统计信息并将其存储在某个地方。
问题是,每个数据库的“某处”都不同。在Oracle中,它们位于ALL_TAB_STATISTICS和ALL_TAB_COL_STATISTICS表中;在MySQL中,它们位于INFORMATION_SCHEMA.TABLES和INFORMATION_SCHEMA.STATISTICS中,依此类推。
JDBC声称通过DatabaseMetaData.getIndexInfo方法提供信息。但这并不适用于所有驱动。(我唯一尝试过的MySQL,尽管是一个相当旧的版本,但没有给我任何行数统计数据。)
假设我们引入了一个SPI来获取表和列的统计信息:
package mondrian.spi;
import javax.sql.DataSource;
interface StatisticsProvider {
int getColumnCardinality(DataSource dataSource, String catalog,
String schema, String table, String[] columns);
int getTableCardinality(DataSource dataSource, String catalog,
String schema, String table);
}
以及几种实现方式:
- 一个备用的实现类SqlStatisticsProvider,它生成select count(distinct…)…和select count(*)...查询。
- 使用JDBC方法(如getIndexInfo)的JdbcStatisticsProvider实现类。
- 一种使用每个数据库的特定表、如OracleStatisticsProvider、MySqlStatisticsProvider等的实现类。
每个方言都可以指定一个或多个SPI实现,并按顺序进行尝试。(每个方法都可以返回-1表示“我不知道”。)
结论
统计数据对Mondrian来说是一个重要问题。在现实世界中,遗漏的统计数据比有些不准确的统计数据更具破坏性。如果统计数据不准确,Mondrian将低效地执行查询,但如果统计数据在一个数量级内,则与最佳性能的差异可以忽略不计;缺少统计数据会导致Mondrian生成潜在的昂贵SQL语句,尤其是在当天至关重要的第一个查询期间。
提出了两个解决方案:
- 自动填充工具将以一种方式解决问题,但代价是计划工具的运行。
- 统计数据提供者利用数据库自己的统计数据。 它以通常的开源方式解决了多样性问题:它提供了 SPI,并让社区为他们最喜欢的数据库提供 SPI 的实现。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。