TFDTable.FieldDefs.Update执行缓慢的原因及优化方案咨询
问题描述
我需要用TFDTable获取PostgreSQL(Oracle、SQL Server等数据库均可)中一批表的字段定义(字段名、类型及大小)。遇到的问题是:某表含20列、500万条记录,执行MyFDTable.FieldDefs.Update;需要20秒(如果仅修改TableName属性不调用该方法,各表会获取到相同的字段定义)。
对比旧版BDE的TTable(通过ODBC)执行相同方法仅需1秒;当表包含大尺寸BLOB列、总表大小达20GiB时,TFDTable的该方法需执行20分钟,而TTable仍仅需1秒。
我猜测FireDac在FieldDefs.Update期间隐式执行了Open和FetchAll,但不清楚具体原因。
FireDac连接示例代码
MyFDCon.Params.Add('DriverID=PG'); MyFDCon.Params.Add('Server=MyPgServerIP'); MyFDCon.Params.Add('Port=5432'); MyFDCon.Params.Add('Database=db1'); MyFDCon.Params.Add('CharacterSet=utf8'); MyFDCon.Connected := True; MyFDTable.Connection := MyFDCon; MyFDTable.TableName := MyTableName; MyFDTable.FieldDefs.Update; // 大表下速度极慢 // 遍历字段定义 for i := 0 to MyFDTable.FieldDefs.Count - 1 do begin ... end;
BDE连接示例代码
MyBdeTable.DatabaseName := MyODBCDSN; MyBdeTable.TableName := MyTableName; MyBdeTable.FieldDefs.Update; // 即使大表也很快 // 遍历字段定义 for i := 0 to MyBdeTable.FieldDefs.Count - 1 do begin ... end;
我的问题:
- 为何FireDac刷新字段定义的速度远慢于BDE?
- 是否有办法加速该调用?是否存在相关表选项可控制?
- 是否有其他无需查询系统表的更快方式获取表字段定义?
备注:因需支持64位,无法使用BDE。
回答
1. FireDac速度慢的原因
FireDac的FieldDefs.Update默认行为与BDE的TTable有本质区别:
- 元数据获取范围不同:FireDac为了提供跨数据库的统一元数据支持,默认会查询更多额外信息,比如字段的默认值、约束、统计信息、索引等,这些都需要访问多个系统表并做关联查询,开销极大;而BDE的
TTable通过ODBC直接调用底层的元数据接口,仅获取最基础的字段名、类型、长度等核心信息,不会额外查询其他元数据。 - 隐式操作差异:部分FireDac数据库驱动(比如PostgreSQL)在更新字段定义时,可能会隐式执行
SELECT * FROM 表名 LIMIT 0来获取结果集结构,但如果驱动对该语句的处理不够优化,或者表存在大字段(如BLOB),会导致额外的资源开销;而BDE不会执行这类语句,直接从数据库元数据缓存或系统表中读取最简信息。 - 兼容性设计开销:FireDac作为新一代数据访问组件,需要兼容更多数据库特性和版本,内部做了更多的兼容性校验和转换逻辑,这也会增加执行时间。
2. 加速FieldDefs.Update的方法
可以通过调整FireDac的参数和选项,减少不必要的元数据查询,从而大幅提升速度:
- 限制元数据获取类型:设置
TFDTable.ResourceOptions.MetadataItemTypes := [mitFields],仅获取字段基础信息,不查询索引、约束、触发器等其他元数据。 - 关闭默认值获取:设置
TFDTable.ResourceOptions.FetchDefaults := False,避免查询字段的默认值信息,减少系统表访问。 - 指定数据库模式:对于PostgreSQL,在连接参数中添加
MetaDefSchema=public(替换为你的实际模式),缩小元数据查询的范围,避免遍历所有模式。 - 使用连接对象的
GetFieldDefs方法:直接调用MyFDCon.GetFieldDefs(MyTableName, MyFDTable.FieldDefs),跳过TFDTable内部的一些额外校验逻辑,直接获取字段定义。 - 调整Open选项:如果需要临时打开表获取字段定义,可以设置
MyFDTable.OpenOptions := [ooFetchMetadataOnly],然后调用MyFDTable.Open,此时仅获取元数据不加载任何数据,之后再通过FieldDefs读取信息,速度也会比FieldDefs.Update快。
3. 无需手动查询系统表的更快方式
除了上述优化后的FieldDefs.Update,还可以通过以下方式快速获取字段定义,且无需自己编写系统表查询语句:
- 使用
FDCommand执行空结果集查询:创建TFDCommand,执行SELECT * FROM 表名 LIMIT 0(PostgreSQL)或SELECT TOP 0 * FROM 表名(SQL Server),然后通过FDCommand.FieldDefs获取字段定义。这种方式直接利用数据库返回的结果集结构,不会查询额外元数据,速度接近BDE的表现。var FDCommand: TFDCommand; begin FDCommand := TFDCommand.Create(nil); try FDCommand.Connection := MyFDCon; FDCommand.CommandText.Text := 'SELECT * FROM ' + MyTableName + ' LIMIT 0'; FDCommand.Prepare; FDCommand.FieldDefs.Update; // 遍历字段定义 for i := 0 to FDCommand.FieldDefs.Count - 1 do begin ... end; finally FDCommand.Free; end; end; - 利用FireDac的元数据缓存:首次获取字段定义后,将其缓存到本地(比如字典中),后续相同表直接读取缓存,避免重复查询数据库。
内容的提问来源于stack exchange,提问作者Petro K
相关产品推荐
相关产品推荐

