一说到 GBase 8s 的索引查询,很多刚从 MySQL、Oracle 转过来的同学第一反应就是输入 SHOW INDEX FROM 表名,结果 GBase 8s 直接报语法错误。再不然就是用 Oracle 的习惯去翻 ALL_INDEXES、USER_INDEXES,发现数据也不对。其实这不怪大家,GBase 8s 骨子里就是 Informix 血统,索引信息统一放在系统目录表(System Catalog)里,查询索引用的是标准 SQL,不是专用命令。这文章就是把“怎么用命令查索引”这件事彻底讲清楚:先搞懂索引信息存在哪,再给你能直接抄的查询 SQL,最后补上日常维护和排障时一定会用到的工具命令。内容适合刚接触 GBase 8s 的开发、运维和 DBA,哪怕你之前完全没碰过 Informix 系数据库,照着一步步也能独立查明白。
1. 索引查询前的必备知识
1.1 为什么 GBase 8s 没有 SHOW INDEX
先说个基础概念:GBase 8s 的索引信息不像 MySQL 那样集中在 information_schema.statistics 视图里,也不像 Oracle 那样分散在 DBA_INDEXES、DBA_IND_COLUMNS 这些数据字典视图中。它采用了一套更“复古”但非常稳定的设计——系统目录表,这些表以 sys 开头,存在每个数据库自己的系统目录里。
这意味着三件事:
- 索引信息可以通过普通 SELECT 语句查询,不需要额外授权,也不需要管理工具。
- 查询方式高度统一,掌握了
systables、sysindexes、syscolumns这几张表,就掌握了所有表和索引的元数据。 - 不同版本之间系统表结构基本稳定,你写的查询脚本可以在多个版本沿用,不会一升级就崩。
很多人刚接触时觉得麻烦,但用熟了反而觉得比 SHOW INDEX 更灵活。你可以按任意条件组合筛选,比如“查所有表里没建索引的外键列”,这种需求如果用 MySQL 写起来很费劲,但在 GBase 8s 里其实就是一条多表关联 SQL。
1.2 核心系统表:systables、sysindexes、syscolumns
GBase 8s 中和索引关系最密切的三张系统表,我的习惯是先记住它们各自管什么。
systables:存储数据库里所有表的基本信息,包括表名 tabname、表 ID tabid、属主 owner、行数估算 nrows、列数 ncols、索引数 nindexes、分区号 partnum 等。注意它不只是用户表,系统目录表本身也存在里面,所以查询时通常要加过滤条件,把系统表排除掉。
sysindexes:存储所有索引的定义。这里一个比较特别的地方是,它用 part1 到 part16 这 16 个字段来记录索引键列的顺序和列号,列号对应的是 syscolumns.colno。也就是说,想看一个索引包含哪些列,不是直接查一个数组字段,而是要把这 16 个字段拼出来和 syscolumns 做关联。这算是 Informix 系数据库比较经典的设计。
syscolumns:存储所有表的列定义,包括列名 colname、列号 colno、列类型 coltype、列长度 collength 等。它是把索引键列号“翻译”成列名的关键。
这三张表的关系一句话概括:systables 是表的“户口本”,syscolumns 是列的“户口本”,sysindexes 是索引的“登记簿”,索引通过 tabid 指向表,通过 partN 字段指向列。
1.3 索引类型字段 idxtype 的含义
查索引时,sysindexes 里的 idxtype 字段是最先要认识的。它是一个单字符字段,常见取值如下:
| idxtype 取值 | 含义 | 说明 |
|---|---|---|
| U | Unique Index | 唯一索引,不允许重复键值 |
| D | Duplicate Index | 普通索引,允许重复键值 |
| P | Primary Key Index | 主键索引,通常由主键约束自动创建 |
实际上,你在表上定义了主键约束,GBase 8s 会自动创建一个 idxtype='P' 的索引;定义了唯一约束,会自动创建 idxtype='U' 的索引;手工执行 CREATE INDEX 没带 UNIQUE,默认就是 idxtype='D' 的普通索引。
所以当你看到一张表的索引清单时,只需要扫一眼 idxtype 就能判断出哪些是约束自动创建的、哪些是开发手动创建的。这对排查重复索引特别有用,因为很多系统里的冗余索引,本质上就是“开发手动建了一个唯一索引,又通过约束建了一个一样列组合的索引”。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心查询命令:用 SQL 直查系统表
2.1 查一张表的全部索引清单
假设我们要查 orders 表上所有索引,最直接的 SQL 是关联 systables 和 sysindexes:
sql复制SELECT a.tabname,
b.idxname,
b.idxtype,
b.owner
FROM systables a,
sysindexes b
WHERE a.tabid = b.tabid
AND a.tabname = 'orders'
AND a.tabid > 99;
这里几个点需要单独解释。
a.tabid > 99 是为了排除系统目录表。GBase 8s 中系统目录表的 tabid 通常在 1 到 99 之间,用户表从 100 以后开始分配。虽然查询 orders 这种表名系统表里不会有重名,但加上这个条件能避免你后面写“查所有用户表索引”的脚本时把系统表也带出来,这是个好习惯。
b.idxtype 这个字段我们上面说了,U 是唯一索引,D 是普通索引,P 是主键索引。
b.owner 是索引的属主,正常情况下和表的属主一致。
执行结果大概长这样:
| tabname | idxname | idxtype | owner |
|---|---|---|---|
| orders | pk_orders | P | informix |
| orders | ord_cust_idx | D | informix |
| orders | ord_date_uniq | U | informix |
实际使用中,我通常会加上 nindexes 字段验证一下。比如 systables 里写 orders 的 nindexes=3,但 sysindexes 关联出来只有 2 条记录,那说明系统表统计信息可能有偏差,或者有索引处于一种异常状态。这种情况我在生产环境遇到过,后面维护部分细说。
2.2 把索引列也带出来
光知道索引名字和类型还不够,99% 的场景下你得知道“这个索引到底建在哪些列上”。在 GBase 8s 里,这一步就是和 syscolumns 关联。
先看一种最直观的写法:
sql复制SELECT i.idxname,
c.colname,
i.part1,
i.part2,
i.part3,
i.part4
FROM sysindexes i,
systables t,
syscolumns c
WHERE i.tabid = t.tabid
AND i.tabid = c.tabid
AND t.tabname = 'orders'
AND c.colno IN (i.part1, i.part2, i.part3, i.part4)
ORDER BY i.idxname, c.colno;
这里 IN 子句能把索引键列对应的列名列出来,但有个细节要注意:结果集会按 idxname 返回,列的顺序可能和索引里定义顺序不一致。
如果你要严格按索引键顺序展示,可以这样写,给每个键列位一个明确的排序优先级:
sql复制SELECT i.idxname,
c.colname,
CASE c.colno
WHEN i.part1 THEN 1
WHEN i.part2 THEN 2
WHEN i.part3 THEN 3
WHEN i.part4 THEN 4
END AS key_seq
FROM sysindexes i,
systables t,
syscolumns c
WHERE i.tabid = t.tabid
AND i.tabid = c.tabid
AND t.tabname = 'orders'
AND c.colno IN (i.part1, i.part2, i.part3, i.part4)
ORDER BY i.idxname, key_seq;
这个 CASE WHEN 写法看起来啰嗦,但实际价值很大。你一眼就能看出这是一个复合索引,第一列是哪个、第二列是哪个,完全按 B-tree 的键顺序对齐。在判断 SQL 是否能用上索引、是否违反了“最左前缀”原则时,这种输出格式非常有用。
如果你建的索引超过 4 个键列,需要把 part5 到 part16 也补上 CASE WHEN,逻辑完全一样,就是代码长一点。我习惯把它封装成一个视图或者存储过程,后面维护会省很多事。
2.3 主键、唯一约束与索引的关联查询
很多 DBA 排查问题时,真正要查的不是索引本身,而是“这个索引到底是被哪个约束创建的”。比如你删一个索引,结果报错说“索引被约束使用”,这时候你就需要知道约束和索引的对应关系。
GBase 8s 里约束信息存在 sysconstraints 表中:
| 字段 | 含义 |
|---|---|
| constrid | 约束 ID |
| constrname | 约束名称 |
| tabid | 所属表 ID |
| constrtype | 约束类型 |
| idxname | 关联的索引名称 |
constrtype 的常用取值我记得比较清楚的有这么几个:
P:主键约束U:唯一约束F:外键约束(引用约束)C:检查约束R:非空约束
实际场景中,你更常见的是拿 sysconstraints 和 sysindexes 做个外连接,看哪些索引是“孤儿索引”(没有关联任何约束),哪些约束的索引丢了:
sql复制SELECT t.tabname,
i.idxname,
i.idxtype,
c.constrname,
c.constrtype
FROM systables t,
sysindexes i
LEFT JOIN sysconstraints c
ON c.idxname = i.idxname
AND c.tabid = i.tabid
WHERE t.tabid = i.tabid
AND t.tabname = 'orders'
ORDER BY i.idxname;
这里用 LEFT JOIN 而不是普通关联,是为了把没有对应约束的索引也显示出来,constrname 和 constrtype 为 NULL 的就是手工创建、不依赖约束的索引。
很多人分不清“索引”和“约束”的区别,简单理解:约束是逻辑规则,索引是物理结构。主键约束保证数据不重复,它底层用唯一索引实现;但反过来,唯一索引并不一定是约束,可能只是开发为了查询性能手动加的唯一性索引。
2.4 按条件筛选有效索引
掌握了上面的表结构,你完全可以写出一些比较高阶的“索引分析”查询。比如:
查所有用户表里,没有主键索引的表:
sql复制SELECT t.tabname,
t.tabid
FROM systables t
WHERE t.tabid > 99
AND t.tabtype = 'T'
AND NOT EXISTS (
SELECT 1
FROM sysindexes i
WHERE i.tabid = t.tabid
AND i.idxtype = 'P'
);
这个查询在生产环境做“表结构健康检查”时非常有用。很多业务系统上线几年后,新加的表没有规范建主键,通过这一条 SQL 就能把所有漏网之鱼捞出来。
再比如,查出所有重复的索引组合。这里的思路是把 part1 到 part4 拼接成字符串,然后按表分组统计:
sql复制SELECT tabid,
idxname,
part1 || ',' || part2 || ',' || part3 || ',' || part4 AS key_cols,
COUNT(*) OVER (PARTITION BY tabid, part1, part2, part3, part4) AS dup_cnt
FROM sysindexes
WHERE part1 IS NOT NULL
ORDER BY key_cols, dup_cnt DESC;
这个查询在索引治理时算是“杀手锏”。一张几千万元的订单表,如果有两个完全相同的列组合索引,意味着每次插入都要维护两颗 B-tree,写入性能白白被拖累。这种脚本查出来之后,结合业务 SQL 再确认保留哪一个,删除另外一个,效果立竿见影。
3. 命令工具组合拳:dbaccess、onstat、oncheck
3.1 dbaccess 里最快的查看方式:INFO 命令
如果你只是临时想看一眼某张表的索引,不一定要写 SQL。GBase 8s 自带的命令行工具 dbaccess 提供了一组 INFO 命令,比手写系统表查询快得多。
用法是先进到目标数据库,然后在 SQL 编辑界面执行:
sql复制INFO INDEXES FOR orders;
也能查列:
sql复制INFO COLUMNS FOR orders;
INFO INDEXES FOR 表名 的输出比较简洁,会把索引名、索引类型、索引键列都按顺序列出来。它本质上也是读取系统目录表,只是帮你把 sysindexes、syscolumns 的关联逻辑封装好了。
不过它有个局限:输出格式不适合批量处理,也不方便做复杂筛选。比如你想把整个库里所有表的索引一次性导出来,用 INFO 就完全不行,还得回到我们前面说的系统表 SQL。
我的习惯是:快速查看单表单索引用 INFO INDEXES,批量收集、复杂分析用自定义 SQL,两者互补。
3.2 用 onstat 辅助定位索引状态
SQL 能告诉你“索引定义是什么”,但索引在实例里“运行得怎么样”,有时候就要借助 onstat 工具族。
先介绍 onstat -t,它用来查看实例中当前已打开的表相关信息,输出里能关联到表的分区号 partnum。当你怀疑某个大表的索引在做全表扫描时,可以结合 onstat -g 系列命令看会话状态。
再比如索引锁等待问题,可以用 onstat -k 看当前锁信息。索引页被锁住之后,并发插入会遇到明显的锁等待,onstat -k 输出的行锁信息中能看到对应的 tblnum,你可以拿着这个数字反查 systables.partnum,定位到底是哪张表的索引键上锁了。
不过要提醒一句,onstat 命令的输出不同版本有些差异,使用时先看 onstat - 的帮助菜单,确认你当前版本支持哪些参数。另外,这些命令多数需要数据库实例的系统管理员权限,普通业务账号执行不了。
3.3 oncheck 检查索引物理一致性
索引定义层面的问题用 SQL 查,索引物理结构有没有损坏,就要用 oncheck 了。
oncheck 是 GBase 8s 提供的一致性检查工具,可以检查数据页、索引页、B-tree 结构等。最常用的一个场景是,数据库日志里出现 “index page ... corrupt” 之类的错误,或者某个查询结果明显异常,怀疑索引损坏时,可以执行:
bash复制oncheck -ci 数据库名:表名
这里的 -c 表示检查(check),-i 表示索引(index)。实际执行时它会遍历表的索引,逐个键值验证 B-tree 结构,同时和表数据行做比对。如果发现不一致,输出里会有明显的 ERROR 信息。
我遇到过的一次情况是:某个表正常 SELECT 没问题,但带 WHERE 条件查询时结果少了几行,后来排查发现是索引页出现了逻辑损坏,最后通过重建索引解决。当时如果没有 oncheck 快速定位到具体索引,可能还要花大半天时间做数据对账。
需要注意:oncheck 是物理检查工具,执行时要小心资源消耗。对一张几十亿行的大表跑全索引检查,会占用较多 IO 和 CPU,建议放在业务低峰期执行,并且先在测试环境验证一下耗时。
4. 查询之后:索引维护与问题排查
4.1 统计信息过旧会让查询结果“骗人”
先泼一盆冷水:你能查出索引,不代表优化器会用它。GBase 8s 的优化器是靠统计信息来决定是否走索引的,统计信息过旧,索引建得再好也可能被弃用。
怎么判断统计信息是否为最新?系统表里没有直接的时间戳,但你可以通过执行计划间接判断。假设你查一张上亿行的表,过滤条件命中率只有 2%,优化器却选择了全表扫描,大概率就是统计信息没更新。
这时候执行统计信息更新:
sql复制UPDATE STATISTICS FOR TABLE orders;
也可以只更新某个索引对应的统计信息:
sql复制UPDATE STATISTICS FOR TABLE orders (idx_orders_cust);
UPDATE STATISTICS 有很多选项,比如:
sql复制UPDATE STATISTICS HIGH FOR TABLE orders;
HIGH 选项会采集更详细的列分布信息,帮助优化器做出更准确的估算,但执行时间更长、资源消耗更大。日常维护中我的习惯是:大表用 MEDIUM 或默认级别,周期性跑;小表可以直接 HIGH,反正代价不高。
有一类情况要特别注意:大量数据分批入库后,nrows 这类系统表里的估算值可能严重不准。你查 systables 时候看到某个表只有几千行,实际已经有几百万行,这种环境下优化器很容易做出全表扫描的决定。手动执行一次 UPDATE STATISTICS,很多时候比加索引还管用。
4.2 索引过多和碎片化怎么处理
索引不是越多越好。每多一个索引,写入和更新时就要多维护一棵 B-tree。业务上经常遇到的问题是:系统上线时间长了,各种临时索引、重复索引堆积,到后期数据库慢,大家第一反应是加索引,结果越加越慢。
我的建议是先用前面 2.4 节的重复索引查询脚本,把列组合完全一样的索引筛出来,和开发确认后合并。再看哪些索引是低基数列(比如性别、状态码这类只有几个取值的列),这种列的索引选择性很低,除非业务上有非常特定的需求,否则索引带来的收益远小于维护成本。
索引碎片化则是另一个隐形杀手。频繁的删除、更新会让索引页出现大量空洞,此时索引遍历的物理页数增加,逻辑上看着索引没变,实际扫描性能下降很厉害。
处理碎片化最直接的方法是重建索引:
sql复制DROP INDEX idx_orders_cust;
CREATE INDEX idx_orders_cust ON orders (cust_id);
对于大表,纯手工 DROP 再 CREATE 会有一段索引缺失的空窗期,期间相关查询会退化成全表扫描,所以要在停机窗口操作。如果用的是 GBase 8s 企业版,可以评估是否支持在线重建索引的能力;如果不行,就得做好业务降级预案。
4.3 排障实操:常见索引问题速查
下面这几种情况,是我实际排查中遇到频次最高的,整理成一个速查表:
| 现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 查询有条件但不走索引 | 统计信息过旧 | 看执行计划,对比实际数据量 | 执行 UPDATE STATISTICS FOR TABLE |
| 索引明明存在却报“找不到索引” | 库名或表名大小写不对 | 用 INFO INDEXES FOR 核对 |
确认库表标识符规范 |
| DROP INDEX 报“被约束使用” | 索引由主键/唯一约束创建 | 关联 sysconstraints 查询 |
先 DROP 约束,再处理索引 |
| 索引页损坏 | 物理介质问题或异常宕机 | 用 oncheck -ci 检查 |
重建索引,检查硬件 |
| 相同列组合多个索引 | 开发临时创建未清理 | 用重复索引脚本筛选 | 合并索引,删除冗余 |
| 复合索引没生效 | 查询条件未遵循最左前缀 | 对比索引键顺序和 WHERE 条件 | 调整索引列顺序或改写 SQL |
这里重点说下大小写问题。GBase 8s 如果建表时用了双引号括起小写表名,那么查询 systables.tabname 时就必须用小写去匹配;如果建表时没加双引号,表名会被统一转成大写。很多人明明是同一张表,查 tabname = 'orders' 查不到,就是因为实际存储的是大写 ORDERS。遇到这种问题别先怀疑系统表坏了,先看一下 tabname 里到底存的是什么。
还有个常见坑是复合索引的顺序判断。同样的两个列 (a, b) 和 (b, a) 是完全不同的两个索引,前者对 WHERE a = ? AND b = ? 有效,对 WHERE b = ? 基本无效。查询时要把 part1、part2 的顺序列出来,不要只看列名集合。
5. 索引查询的长期维护建议
最后再分享一点个人的实操习惯。
我现在每接手一套 GBase 8s 环境,第一件事不是去看业务代码,而是先跑一遍索引体检脚本:把所有用户表的索引名、类型、键列、约束关联关系导成一份清单,放到专门的维护文档里。这看起来是个笨办法,但后面排查问题时的效率会高很多,因为很多索引问题光靠临时查询是看不出历史脉络的。
日常巡检中,我建议至少每周跑一次统计信息更新,UPDATE STATISTICS FOR DATABASE 这种全库级别的更新在数据量大的环境要谨慎,但如果能放在凌晨低峰期执行,收益是很大的。索引碎片方面,可以每月或者每季度检查一次,重点看高并发写入的大表,不要所有表一刀切,否则维护成本会让人吃不消。
另外,GBase 8s 的官方文档里对 sysindexes、sysconstraints 等系统表有标准字段说明,遇到拿不准的字段含义,优先查文档,别自己瞎猜。系统表结构虽然稳定,但不同小版本间也可能有细微差异,尤其是 sysfragments 这类分区相关的表,字段含义更复杂,我在生产环境就吃过一次“想当然”的亏,后来每次都用文档核对一遍。
索引查询这件事,看着只是几条 SELECT 语句,实际上背后是数据字典设计、约束管理、优化器行为、物理存储结构一整套知识链。把系统表这条线摸透了,GBase 8s 的绝大多数元数据问题你都不会再觉得难。希望这篇内容能给你省下一点摸索的时间,下次再碰到“索引为什么没用上”“这张表有哪些索引”“哪个索引是冗余的”这类问题,直接照着上面的方法查就行。
