关于1.7 TB Firebird SQL数据库的更多详细信息
Alexey Kovyazin,2014年5月26日
几天前,我们发布了一篇关于我们测试的文章,专门讨论Firebird性能与数据库增长之间的关系,其中我们测试了(包括其他)1.7TB的Firebird SQL数据库。在这里,您可以找到关于这样一个大型数据库的更多细节。
表
在表1中,您可以找到带有关键特征的表列表。此统计数据是使用gstat -a -r获取,并通过我们的IBAnalyst工具进行解释的。
| 表 | 记录数 | 记录长度,字节 | 数据页 | 表大小,Mb | 索引大小,Mb | 总计,% |
|---|---|---|---|---|---|---|
| ORDER_LINE | 6300024797 | 60.09 | 38880344 | 607505.3 | 50582.3 | 34 |
| STOCK | 2100000000 | 298.88 | 43783806 | 684121.9 | 15809.61 | 39 |
| ORDERS | 630004141 | 29.00 | 2692324 | 42067.56 | 4250.38 | 2 |
| HISTORY | 630000434 | 48.77 | 3446455 | 53850.86 | 0.00 | 3 |
| CUSTOMER | 630000000 | 577.52 | 24226054 | 378532.0 | 8675.78 | 21 |
| NEW_ORDER | 188999981 | 13.00 | 623764 | 9746.31 | 1260.84 | 1 |
| DISTRICT | 210000 | 103.92 | 1860 | 29.06 | 1.27 | ~0 |
| ITEM | 100000 | 82.73 | 756 | 11.81 | 0.52 | ~0 |
| WAREHOUSE | 21000 | 97.90 | 179 | 2.80 | 0.11 | ~0 |
表1. 1.7TB Firebird SQL数据库中的表及其主要参数
如您所见,最大的表是ORDER_LINE–它包含超过60亿条记录。其大小约为600GB,并且该表有50GB的索引。该表占数据库的34%。
表STOCK也非常大–它包含约20亿条记录。尽管记录数少于表ORDER_LINE,但STOCK在数据库中占用约680GB(39%),因为STOCK中的记录长度为298.88字节,而ORDER_LINE中仅为60.09字节。相应地,STOCK相关索引的大小仅为15GB。
第三大的表CUSTOMER,其记录长度更大–577.52字节,仅630百万条记录就占用了378GB(21%)。
索引
这个数据库中的索引不多,因为它仅为测试而设计–大多数表只有1个索引–主键。在现实世界的应用中,开发人员会创建许多索引来满足特定用户请求,但这里的索引集中在最快的数据插入和更新上–因此,索引大小仅为表大小的5-10%,而通常为30-50%(如果您的索引大小超过表大小的50%,请考虑重新设计数据库架构,或者,可能删除无用或低效的索引)。
在表2中,您可以找到此数据库中所有索引及其关键特征的列表,这些数据取自gstat统计并由IBAnalyst分析:
| 索引 | 表 | 深度 | 键数 # | 键长度,字节 | # 唯一键 | 大小,Mb |
|---|---|---|---|---|---|---|
| ORDER_LINE_PK | ORDER_LINE | 4 | 6300024797 | 1.41 | 6300024797 | 50582.34 |
| STOCK_PK | STOCK | 4 | 2100000000 | 1.00 | 2100000000 | 15809.61 |
| ORDERS_PK | ORDERS | 3 | 630004141 | 1.01 | 630004141 | 4250.38 |
| CUSTOMER_LAST | CUSTOMER | 3 | 630000000 | 0.00 | 1000 | 4029.64 |
| CUSTOMER_PK | CUSTOMER | 3 | 630000000 | 1.01 | 188999981 | 1260.84 |
| NEW_ORDER_PK | NEW_ORDER | 3 | 188999981 | 1.01 | 188999981 | 1260.84 |
| DISTRICT_PK | DISTRICT | 2 | 210000 | 1.37 | 210000 | 1.27 |
| ITEM_PK | ITEM | 2 | 100000 | 1.00 | 100000 | 0.52 |
| WAREHOUSE_PK | WAREHOUSE | 2 | 21000 | 1.00 | 21000 | 0.11 |
表2. Firebird 1.7TB数据库的索引
如您所见,所有索引的键大小都非常小–最大为1.41,这意味着索引被非常有效地压缩–例如,ORDER_LINE的主键在50GB空间中包含60亿个键。这是一个非常好的结果。
有2个索引的深度为4。这意味着引擎需要执行4次索引页读取才能找到请求的值。建议索引深度不应高于3,如果更高,则应增加数据库页面大小。但是,我们在这个数据库中已经使用了最大页面大小(16Kb),所以我们需要接受这一点。
预热
在我们继续查询之前,让我们进行“预热”。当数据库很大时,与表关联的系统数据也很大,Firebird需要一些时间来缓存必要的系统页面。例如,表ORDER_LINE有10110个指针页。预热对于任何大型数据库都是必要的。
没有预热,第一次执行查询将比预热后花费显著更长的时间。
为了预热数据库缓存,让我们执行一系列简单查询将系统数据加载到缓存中:
select first 1 * from ORDER_LINE; select first 1 * from STOCK; select first 1 * from customer; select first 1 * from ORDERS; select first 1 * from ITEM;
对于这样简单的数据架构来说,这已经足够了。对于具有数百个表的大型复杂数据库,预热架构可能要复杂得多,需要仔细开发。
结果,Firebird进程(Firebird SuperServer 2.5.2 64位)的内存消耗增长:

图1. 数据库缓存预热
查询
预热后,我们可以运行几个典型查询来评估用户将如何使用如此大的Firebird数据库:典型查询和读操作的响应时间将是多少。
| 查询 | 计划和统计 | 描述 |
|---|---|---|
select first 10 w_id, w_name, c_id, c_last<br> from WAREHOUSE, customer<br> where c_w_id = w_id and c_w_id = 10000 |
PLAN JOIN (WAREHOUSE NATURAL,<br> CUSTOMER INDEX (CUSTOMER_PK))<br> STATISTICS<br> Current memory = 166919744<br> Delta memory = 5616<br> Max memory = 166977488<br> Elapsed time= 0.08 sec<br> Buffers = 10000<br> Reads = 1<br> Writes 0<br> Fetches = 51 |
连接表WAREHOUSE(21000条记录)与CUSTOMER(6.3亿条记录),条件为特定仓库 |
select count(*)<br> from WAREHOUSE, customer<br> where c_w_id = w_id and c_w_id = 10000 |
PLAN JOIN (WAREHOUSE INDEX (WAREHOUSE_PK),<br> CUSTOMER INDEX (CUSTOMER_PK))<br> COUNT<br> ============<br> 30000<br> <br> Current memory = 166918560<br> Delta memory = -1184<br> Max memory = 166977488<br> Elapsed time= 0.16 sec<br> Buffers = 10000<br> Reads = 1175<br> Writes 0<br> Fetches = 60040 |
计算上一个查询的记录数。 |
SELECT first 10 *<br> FROM ORDER_LINE<br> WHERE OL_W_ID = 10050 |
PLAN (ORDER_LINE INDEX (ORDER_LINE_PK))<br> STATISTICS<br> Current memory = 167013640<br> Delta memory = 95080<br> Max memory = 167055968<br> Elapsed time= 0.80 sec<br> Buffers = 10000<br> Reads = 10285<br> Writes 0<br> Fetches = 10424 |
对最大的表ORDER_LINE(约60亿条记录)进行查询,条件为选择指定仓库ID的order_line记录。由于主键ORDER_LINE_PK是复合的,并包含仓库ID(CONSTRAINT ORDER_LINE_PK: Primary key (OL_W_ID, OL_D_ID, OL_O_ID, OL_NUMBER),因此在此查询中有效使用。 |
SELECT count(*)<br> FROM ORDER_LINE<br> WHERE OL_W_ID = 10050; |
PLAN (ORDER_LINE INDEX (ORDER_LINE_PK))<br> <br> COUNT<br> ============<br> 299509<br> Current memory = 167125464<br> Delta memory = -7160<br> Max memory = 167267976<br> Elapsed time= 3.41 sec<br> Buffers = 10000<br> Reads = 1994<br> Writes 0<br> Fetches = 599170 |
计算上一个查询。具有相同参数的第二个查询会快得多,但具有不同参数的查询显示类似结果。 |
Select first 10 w_id, w_name, c_id, c_last<br> rom WAREHOUSE, CUSTOMER<br> where c_w_id = w_id and (c_w_id > 8000)<br> and (c_w_id < 10000) |
PLAN JOIN (WAREHOUSE INDEX (WAREHOUSE_PK),<br> CUSTOMER INDEX (CUSTOMER_PK))<br> <br> STATISTICS<br> Current memory = 167687664<br> Delta memory = -651888<br> Max memory = 168931400<br> Elapsed time= 0.19 sec<br> Buffers = 10000<br> Reads = 31<br> Writes 0<br> Fetches = 63 |
带有连接和2个条件的查询。 |
| select first 10 c1.C_ID, c1.C_FIRST, c1.C_LAST, o1.O_ID,<br> o1.O_OL_CNT, i1.I_NAME, i1.I_PRICE, ol1.OL_AMOUNT<br> from customer c1<br> join orders o1 on (c1.c_w_id = o1.O_w_ID<br> and c1.C_D_ID = o1.O_D_ID and c1.C_ID = o1.O_C_ID)<br> join ORDER_LINE ol1 on (ol1.ol_w_id = c1.C_W_ID<br> and ol1.OL_D_ID = c1.C_D_ID<br> and ol1.OL_O_ID = o1.O_ID)<br> join Item i1 on (ol1.OL_I_ID = i1.i_id)<br> where o1.o_d_id = 1 and o1.o_w_id = 1 and c1.c_id = 10; | PLAN JOIN (C1 INDEX (CUSTOMER_PK),<br> O1 INDEX (ORDERS_PK),<br> OL1 INDEX (ORDER_LINE_PK),<br> I1 INDEX (ITEM_PK))<br> <br> STATISTICS<br> Current memory = 166807128<br> Delta memory = 6016<br> Max memory = 166879200<br> Elapsed time= 0.20 sec<br> Buffers = 9999<br> Reads = 1<br> Writes 0<br> Fetches = 4960 | 查询以显示特定仓库和区域中特定客户的详细信息。它展示了更复杂连接的性能。 |
总结
如您所见,1.7 TB 的 Firebird 数据库在常见读取查询中表现出相当不错的性能。如果查询设计良好并使用合适的索引,即使在非常大的数据库上性能也很出色。当然,也存在一些痛点,比如非常大的数据集的长时间获取,以及计数操作耗时较长(因为 Firebird 会访问所有页面以获取特定事务中特定查询的精确记录数),但这些痛点对于经验丰富的 Firebird 开发者来说是众所周知的,并且有方法可以设计数据库来规避这些问题。
那么插入和更新的性能下降呢?是否可以通过智能调整 Firebird 配置来弥补?
在下一篇文章中,我们将考虑针对 Firebird 1.7 TB 数据库的调优选项–我们将为我们的“小鸟”加速,让它飞起来。