简介:InterBase 6.0 资源包是 Borland 公司推出的关系型数据库管理系统,专为 Delphi 6 等环境下的客户端/服务器应用而设计,适合需要高性能、稳定事务处理能力的中小型项目开发者。整套资料压缩后仅 4.86MB,共 130 个文件,类型覆盖 33 个 C 语言示例源码、15 个可执行程序、8 个动态链接库、6 个帮助文档,以及 SQL 脚本、GDB/GBK 数据库文件、许可协议等;其中既有核心引擎组件 gds32.dll 和安装配置模块,也有卸载程序和接口库文件,可支撑从环境部署到编码调试的全流程。已有 340 人学习下载。借助核心动态链接库与 SQL 示例,读者能快速掌握 InterBase 的连接方式、事务处理和数据定义语句;同时,附带的初始化数据库、帮助文档和许可文件能帮助开发者在 Windows 平台快速搭建本地测试环境,为后续开发企业级 C/S 数据库应用提供清晰落点。
1. InterBase 6.0 是什么:一部能直接落地的旧式嵌入式关系数据库
接到一个跑了十几年的制造执行系统,后端挂着的就是 InterBase 6.0,交接文档里只有一句“别动它”。打开数据目录一看,一个 .gdb 单文件,没有专门的数据库管理员,连备份都是靠复制文件完成的。InterBase 6.0 是 Borland 在 2000 年左右发布的关系型数据库,核心卖点是嵌入式部署、单文件存储和自带多版本并发控制(MVCC),后来 Firebird 就是从它的 6.0 源码分支出去的。它的强项不是跑分,而是省心:不需要专职 DBA,一个文件拷走就能换机器跑。这篇笔记写给要接手老系统、或者正在评估它适不适合做轻量级存储方案的工程师,我会从安装一个能跑的实例开始,把 SQL 写法、事务参数、备份恢复和典型故障全部落到可复现的程度。
2. 在本地跑通 InterBase 6.0:安装、isql 连接与第一条建库语句
2.1 安装前的选择:Windows 版还是 Linux 版
InterBase 6.0 当年的发行形态有 Full、Workgroup、Server 和 Open Edition 几种。Open Edition 是免费开放源码的版本,覆盖单机和有限并发场景,对今天学习评估来说完全够用。但要注意,6.0 是 2000 年代初的产品,在现代 Windows 或新版 Linux 上直接安装会遇到一堆兼容性问题,比如服务起不来、内存分配报错。我实际做旧系统维护时,更常见的做法是在 Windows XP 或 Windows 7 虚拟机里装一套,或者把一个老 Linux 发行版的容器跑起来,再把数据文件放到共享目录里访问。
选型上有一点经验值得说:如果只是要读旧库、做迁移评估,Linux 版更干净,isql、gbak、gfix 这些命令行工具一个不缺;如果是要把旧系统整套重新跑起来,Windows 版带 IBConsole 图形工具,看数据库对象结构、浏览表数据都直观,排错效率高不少。安装完成后,bin 目录下会有 isql、gbak、gfix、gsec、gstat 这几个核心工具,后面所有操作都靠它们完成。
2.2 连接实例:isql 最小命令
isql 是 InterBase 6.0 自带的交互式 SQL 工具,既能连已有库,也能在管理模式下建库。打开一个终端,进入安装目录,执行下面这行:
# 进入 InterBase 6.0 的 bin 目录,Windows 上类似 C:\Program Files\Borland\InterBase\bin cd /opt/interbase/bin # -u 指定用户名,-p 指定密码,默认超级用户是 sysdba,初始密码为 masterkey ./isql -u sysdba -p masterkey命令里-u和-p分别对应用户名和密码,不传数据库名时进入管理会话,可以在里面用 SQL 语句创建新库。InterBase 6.0 有一个默认超级账号 SYSDBA,初始密码是公开的 masterkey,所以装完的第一件事就是改掉它。改密用独立的 gsec 工具:
# 用 gsec 修改 sysdba 的密码,-pw 后面跟的是新密码 ./gsec -u sysdba -p masterkey -modify sysdba -pw s3cr3t_api这条命令的逻辑是先以旧密码登录,-modify指定要改的用户名,-pw给出新密码。改动立即生效,不需要重启服务。密码不要设成空字符串,InterBase 6.0 对弱密码没有任何强制策略,坑都是后面自己踩的。
2.3 建库和建表:第一段能跑的脚本
在 isql 管理会话里输入下面的语句,就完成了一个数据库的创建:
-- 建库,指定文件路径和页大小,页大小直接影响后续性能 CREATE DATABASE '/data/erp.gdb' PAGE_SIZE 4096;页大小是 InterBase 6.0 建库时最不该忽略的参数,它决定单页能装多少行数据,也影响索引项的大小。默认值偏低,如果你预计表里有 VARCHAR(200) 以上字段或者 BLOB,建议直接选 4096 或 8192。页大小在库创建之后就改不了,这是 InterBase 6.0 的硬限制,想后悔只能备份再恢复,所以建库前要想清楚。
建完库接着建表:
-- 员工主表:员工号定长,姓名用变长,备注用 BLOB 存大文本 CREATE TABLE EMPLOYEE ( EMP_ID INTEGER NOT NULL, DEPT_NO CHAR(3) NOT NULL, FULL_NAME VARCHAR(40) NOT NULL, HIRE_DATE DATE, NOTE BLOB );这段 SQL 用的是标准语法,和主流数据库差别不大。有两个点要注意:第一,InterBase 6.0 的 BLOB 是流式存储,可以和其他列混建在同一张表里,但 BLOB 列不能建索引;第二,DATE 类型是 6.0 原生支持的,存日期做范围查询没问题,但 6.0 没有单独的 TIMESTAMP 类型,时间部分要用 TIMESTAMP 或者自己拼字符串,这是个容易踩的坑。
做完这一步,你已经有了一个能连、能建表、能插数据的本地 InterBase 6.0 实例。对一个二十年前的数据库引擎来说,部署复杂度基本上到这里就结束了。
3. 把逻辑写进数据库:域、生成器、触发器与存储过程的 InterBase 6.0 写法
3.1 域(Domain):统一字段定义
InterBase 6.0 的域(Domain)机制类似 PostgreSQL 的 DOMAIN,本质是给字段类型起一个复用名,把公共约束下沉到定义层。做老系统维护时,翻到表结构会发现大量重复的CHAR(1) CHECK (VALUE IN (...)),当年就是用域批量管理状态字段的。先建一个状态域:
-- 员工状态域:A=在职,I=停薪留职,T=离职,默认在职 CREATE DOMAIN DM_EMP_STATUS AS CHAR(1) CHECK (VALUE IN ('A', 'I', 'T')) DEFAULT 'A';建表时直接引用域名,就能省掉重复的 CHECK 约束声明:
CREATE TABLE EMP_STATUS_LOG ( EMP_ID INTEGER NOT NULL, CHANGE_DT DATE NOT NULL, STATUS DM_EMP_STATUS NOT NULL );域的好处是规则只写一处,多个表的同名状态字段行为一致。坏处也很明显:域定义改掉之后,已经建好的表字段不会自动同步,只是新建列时才用新定义。所以老库里的域和表的实际约束经常对不上,排查数据异常时要以表上的实际约束为准,不要只看域定义。
3.2 生成器与自增主键
InterBase 6.0 没有自增列 IDENTITY,自增主键的标准方案是“生成器(Generator)+ 触发器”。生成器是一个全局计数器,取值的原子性由引擎保证,多线程并发取号不会重复。先创建生成器:
-- 建立员工号生成器,并指定起始值 CREATE GENERATOR GEN_EMP_ID; SET GENERATOR GEN_EMP_ID TO 1;实际取值不会直接写在应用里,而是由 BEFORE INSERT 触发器自动填充。下面是完整写法,注意这里的SET TERM是 isql 特有的语法指示:
-- 临时把语句结束符改成 ^,避免过程体内部的分号提前结束语句 SET TERM ^; CREATE TRIGGER TRI_EMP_BI FOR EMPLOYEE ACTIVE BEFORE INSERT AS BEGIN NEW.EMP_ID = GEN_ID(GEN_EMP_ID, 1); END^ -- 恢复默认的分号结束符 SET TERM ;^这段代码里NEW.EMP_ID是行级触发器对当前插入行的引用,GEN_ID(GEN_EMP_ID, 1)表示把生成器当前值加 1 后返回。整个机制和序列 + 触发器的组合思路完全一致,理解上不需要额外负担。为什么要坚持用触发器而不是应用层取号?因为应用层生成主键在并发时容易出现重复,而生成器的原子性由引擎内建锁保证,比应用层锁可靠得多。
SET TERM是新手最容易卡住的点。isql 默认用分号作为语句结束符,但存储过程和触发器内部必然有分号,如果不改结束符,解析器读到第一个分号就认为语句结束了,后面的过程体全部报语法错误。改成^后,解释器会一直读到END^才认为这是完整的一条语句。
3.3 存储过程:旧式 PSQL 语法
InterBase 6.0 的存储过程语法和后来 Firebird 的 PSQL 非常接近,因为它俩本来就是同源。写一个按部门查员工的存储过程:
SET TERM ^; CREATE PROCEDURE GET_EMP_BY_DEPT (DEPT_NO CHAR(3)) RETURNS (EMP_ID INTEGER, FULL_NAME VARCHAR(40)) AS BEGIN FOR SELECT E.EMP_ID, E.FULL_NAME FROM EMPLOYEE E WHERE E.DEPT_NO = GET_EMP_BY_DEPT.DEPT_NO INTO :EMP_ID, :FULL_NAME DO SUSPEND; END^ SET TERM ;^这段过程最核心的是SUSPEND关键字。它会把当前查询出来的这一行放进输出流,然后挂起过程,让客户端读取这一行;客户端读完之后,过程继续执行下一次循环。理解成 Python 的yield最贴切。INTO :EMP_ID里的冒号表示把查询结果赋给输出变量,冒号是 InterBase 6.0 的本地变量引用前缀,少了它会报语法错。
另一个值得注意的地方是参数和输出变量的命名:上面代码中用GET_EMP_BY_DEPT.DEPT_NO来引用输入参数,而不是直接写DEPT_NO。原因是输入参数和输出变量重名时,引擎会优先匹配输出变量,直接写DEPT_NO会被当成输出字段,导致比较条件恒真。这是 InterBase 系列一个非常隐蔽的语义坑,老代码里没少为这个出过诡异数据。调用方式最常见的是用SELECT * FROM GET_EMP_BY_DEPT('001'),和查普通表一样的写法。
4. 事务与并发控制:InterBase 6.0 的 MVCC 原理和 ibconfig 参数调整
4.1 多版本并发控制:为什么读不阻塞写
InterBase 6.0 是商业数据库里最早把多版本并发控制做成默认机制的引擎之一。它的实现思路是:每一行记录在存储层可能同时存在多个版本,每个版本头部记录了自己的创建事务 ID 和一个 back-version 指针,指向更早的版本。当一个事务更新某行时,它不会直接覆盖数据,而是生成一个新版本,旧版本保留给那些还没提交、需要看到旧数据的读事务。
这个机制带来的直接影响是:读事务不需要持有行锁,所以读和写之间天然不互相阻塞。一个长时间运行的报表查询,不会卡住正在执行更新的业务事务;反过来,业务事务的更新也不会让读会话挂起等待。这和后来 MySQL InnoDB 的 MVCC 思路同源,但 InterBase 6.0 在 2000 年就把这套东西放到生产环境里了。
但 MVCC 不是没有代价。旧版本不会被立即清理,它要等到所有活动事务都不再需要它之后才有资格被回收。如果系统里有一个事务长期不提交,那个事务启动前所有的旧版本都得一直留着,数据库文件就这么膨胀起来。这个特性直接引出了 InterBase 运维里最出名的概念:Sweep,下一章单独讲怎么处理。
4.2 三种隔离级别和 WAIT / NO WAIT
InterBase 6.0 的事务隔离级别命名和主流数据库不太一样,初次接触容易搞混。它一共有三种模式,常用的是前两种:
| 事务模式 | 隔离语义 | 适用场景 |
|---|---|---|
| SNAPSHOT | 快照隔离,事务内看到一致的库视图 | 默认选择,报表、批量处理 |
| READ COMMITTED | 读已提交,语句级看到最新数据 | OLTP,短事务、高频更新 |
| CONSISTENCY | 表级稳定,类似 Serializable | 极少用,锁粒度太大 |
在 isql 里通过SET TRANSACTION启用指定模式:
-- 快照隔离:事务启动瞬间的数据库视图固定不变 SET TRANSACTION SNAPSHOT READ WRITE WAIT; -- 读已提交:每次语句都能看到最新已提交数据 SET TRANSACTION READ COMMITTED RECORD_VERSION READ WRITE WAIT;WAIT和NO WAIT决定锁冲突时的行为。遇到别的未提交事务持有的锁时,WAIT 会让当前操作挂起等待,直到锁释放或超时;NO WAIT 则立即返回错误。生产环境我的建议是全都用 WAIT,配合下面的死锁超时参数,让冲突以可控延迟消化掉。
选错隔离级别的实际后果是:长事务选 SNAPSHOT,库膨胀速度会明显加快,因为整个事务存活期间所有中间版本都不能回收;选 READ COMMITTED 但频繁重启事务,锁竞争会增多,更新同一行时更容易撞到锁等待。没有万能的隔离级别,报表类查询用 SNAPSHOT,交易类短事务用 READ COMMITTED,这是老 InterBase 项目里比较共识的分配方式。
4.3 ibconfig 参数:能改的几个地方
ibconfig 是 InterBase 6.0 的全局配置文件,位置在安装根目录下,文件本身是纯文本,直接用编辑器改。它影响的是整个实例,不是单库。我实际调过的参数主要有四个:
| 参数名 | 作用 | 调整方向 |
|---|---|---|
| DATABASE_CACHE_PAGES | 共享页缓存大小,单位是页 | 调大减少磁盘读,但别超过物理内存四分之一 |
| SWEEP_INTERVAL | 自动 Sweep 触发的事务间隔 | 更新密集的库调小,减少膨胀 |
| DEADLOCK_TIMEOUT | 死锁检测等待周期,单位秒 | 锁冲突多的库可以调小到 10 |
| LOCK_TABLE_SIZE | 锁表页数 | 报 lock table overflow 时只能改大后重启 |
修改方式就是在 ibconfig 里找到对应行,把数字换成目标值,保存后重启 InterBase 服务。一个经常被忽略的问题:LOCK_TABLE_SIZE 是固定分配内存结构,如果设得太小,高并发下会报lock table overflow,这个错误只能靠改大参数并重启服务恢复,没有运行时补救手段。所以我一般会把生产库的锁表余量留大一倍,宁可多占点内存,也不在忙时掉链子。
还有一点要提醒:数据库缓存不是越大越好。DATABASE_CACHE_PAGES 如果超过物理内存的一半,操作系统会开始换页,整体性能反而下降。在 6.0 那个内存普遍吃紧的年代,两倍默认值已经算激进调整了。
5. InterBase 6.0 运维避坑手册:gbak 备份、Sweep 与现场排查
5.1 gbak 备份与恢复:当成唯一的后悔药
InterBase 6.0 没有在线热备的图形按钮,最可靠的备份方式是 gbak。它做的是逻辑备份,输出文件是 .fbk,包含数据和元数据。日常全量备份的命令:
# -b 表示备份,-g 表示不整理垃圾版本,备份更快 ./gbak -b -u sysdba -p s3cr3t_api -g /data/erp.gdb /backup/erp-$(date +%Y%m%d).fbk这里的-g参数值得专门说明:它让备份过程不对数据做垃圾版本清理,只原样读出全部数据。好处是备份速度明显更快,坏处是如果你不配合恢复,备份文件会比实际有效数据大。但这不是问题,因为恢复动作本身就是一次物理重建,旧版本会在恢复时被清理掉。
恢复命令是反向的:
# 从备份文件创建新库,-c 表示 create ./gbak -c -u sysdba -p s3cr3t_api /backup/erp-20250301.fbk /data/erp_restore.gdb我强烈建议恢复到一个新文件,而不是覆盖原库。这样原库文件完整保留,恢复失败还能退回;验证新库没问题后,再切换应用的数据源,把旧文件归档。别直接用-r覆盖线上文件,那是给自己挖坑。备份文件也会损坏,多留几天的副本,或者把 fbk 再复制一份到别的机器。
5.2 Sweep 与 OAT:为什么数据文件一直在长
数据库文件的大小动不动就超过实际数据的好几倍,这是 InterBase 6.0 用户最常见的困惑。根源就是第 4 章讲的 MVCC 版本残留。sweep 是引擎自己的空间回收机制,它扫描记录版本链,把连最老活动事务都不需要的旧版本标记为可重用空间。问题是它有代价:sweep 执行时占 CPU 和 IO,如果触发间隔太长,数据文件就会持续膨胀。
手动触发 Sweep 用 gfix:
# 手动触发一次全库 Sweep ./gfix -sweep /data/erp.gdb涉及两个关键概念:OIT(Oldest Interesting Transaction,最老的活跃事务)和 OAT(Oldest Active Transaction,最老的活动事务)。简单说,OIT 是“没有更早事务了”的那个临界点,OAT 是当前最老的活动事务。OIT 越小、和当前事务号差距越大,说明堆积的可回收版本越多,文件膨胀越严重。看到 OAT 长期不动,第一反应应该是去检查应用里有没有挂死未提交的长事务。
5.3 四个典型踩坑现场
现象 1:数据库文件打不开,日志报 corrupt database header。原因:进程异常退出,数据库头页写了一半。解决:先用 gfix 修复头部再看看能不能连:
./gfix -mend /data/erp.gdbmend 只修数据库头,不进数据页。连上之后立刻做一次完整备份,不要继续在原库上跑业务。如果 mended 后还是打不开,就要靠最近一次 .fbk 备份恢复了,这也是为什么前面强调 gbak 要勤做。
现象 2:备份时提示 cannot open backup file。原因:gbak 进程是随数据库服务启动的,它写文件用的是服务账号权限,不是你终端用户的权限。Windows 上把备份路径指向共享目录或非 Administrators 目录最常见。解决:把备份目标改成服务进程有写权限的本地路径,不要用你当前登录账户的桌面位置。
现象 3:插入中文或其他非拉丁字符后,读出来是乱码。原因:建库时没有指定字符集,字段又是 VARCHAR,客户端写入和读取用的字符集不一致。InterBase 6.0 的通用 Unicoded 方案里能选 UNICODE_FSS,它支持用多个字节表示一个字符。解决:连接后先执行字符集声明再读写。
SET NAMES UNICODE_FSS;如果旧库本身建库时没带字符集,列里已经存了乱码,那改连接声明救不了存量数据,只能把数据当出血点清理。所以新建库时强烈建议在 CREATE DATABASE 语句里就带上CHARACTER SET UNICODE_FSS。
现象 4:一个事务卡住不提交,其他事务更新同一行时一直等。原因:InterBase 6.0 的 WAIT 事务会一直挂起等锁,默认死锁超时时间又比较长。解决:先找到那个挂死的会话,把对应事务回滚掉;同时把 ibconfig 里的 DEADLOCK_TIMEOUT 从默认值下调到 10 秒左右,防止下一次再发生类似问题。如果是应用层忘记提交,去改代码,这属于人祸,靠调参只能缓解症状。
6. 日常拿来就能用的验证技巧:gstat 输出读法和迁移判断
接手一个来路不明的老库,我第一件事永远是跑 gstat,而不是直接备份或打开:
# 查看数据库头部信息,不加载业务数据 ./gstat -h /data/erp.gdb关注输出里的三行:OIT、OAT、Next Transaction。如果 OAT 和 Next Transaction 之间隔了几万甚至几十万,说明堆积了大量版本垃圾还没回收,先做一次完整备份,再手动 gfix -sweep。如果 OIT 都还离 Next Transaction 很近,说明这个库事务频率非常低,膨胀风险不大,重点看页大小和文件大小比例。这个判断花一分钟,能决定整个迁移动作的先后顺序。
交接这类老系统时,我还习惯顺手用 gstat -r 看一眼大表的记录版本密度,如果单条记录存在大量 back-version,说明历史上这里经历过失控的并发更新,这类表迁移后要重点关注索引重建。
最后一个判断:如果这个库的数据还要继续用很多年,长期停在 InterBase 6.0 上不是好选择。它是二十多年前的引擎,字符集支持有限、页大小不可改、故障恢复手段少。一个低风险的路径是用 gbak 做全量备份,然后拿 Firebird 的 gbak 恢复它生成的 fbk 文件,把数据迁到同源但持续维护的开源分支上。如果只是读历史数据写个导出脚本,6.0 原库也足够完成历史使命,不必急着折腾。
维持一个老库不倒,靠的不是什么高级技巧,而是固定的检查顺序:gstat 看事务代差,gbak 做备份,恢复验证备份文件能读能查,然后再谈业务迁移。这几年我靠这套流程救回过不少“看着没救”的旧系统,最深的教训是:再老的库,只要备份是刚做的,就有后悔药可吃。希望帮到你。
本文还有配套的精品资源,点击获取