做GBase 8s的DBA时间长了,你会发现“查索引”这件事儿远比想象中重要。不管是日常巡检、性能优化,还是接手一套没人维护的老库,第一件事往往不是去看代码,而是先摸清楚库里的索引长什么样:建了哪些索引、索引建在哪些列上、是不是唯一、碎片严重不严重、优化器到底有没有用上。尤其是GBase 8s这种和Informix同源的数据库,系统目录表和命令风格都有自己的一套逻辑,跟MySQL、Oracle完全不是一个路子,如果按惯性去查information_schema,大概率会碰一鼻子灰。
这篇东西就是给那些正在折腾GBase 8s索引查询的同学准备的。我会从系统目录表讲到实际SQL写法,再讲到onstat命令巡检和执行计划判断,最后带上索引重建和统计信息这些实操内容,尽量把你日常工作里能遇到的“查索引”场景一次性讲透。
1. 查询索引前,先把系统目录这几个表认熟
1.1 GBase 8s 的系统目录和 Informix 一脉相承
很多人第一次接触GBase 8s的时候,第一反应是拿MySQL的思维去查information_schema,结果发现根本没有这张表。再试试Oracle的dba_indexes,同样不存在。真正靠谱的方式是查GBase 8s的系统目录表,也就是sysmaster、sysutils、syscdr这些库下面的那一堆sys开头表。
GBase 8s的架构继承自Informix Dynamic Server,所以它的系统目录设计非常“Informix”。索引相关的信息主要存储在sysindexes这张表里,同时配套了sysindices视图、syscolumns、sysconstraints、systables这几张核心表。理解了这个底层的存储结构,后面写任何查询都顺手。
我平时跟同事说,你只要记住一句话:在GBase 8s里,表的元数据在systables,列的元数据在syscolumns,索引的元数据在sysindexes,约束和索引的关系在sysconstraints。这四张表就是你查索引的左手和右手。
1.2 索引相关系统表各自的职责
用一张表格快速梳理一下,方便你后面对照。
| 表名 / 视图名 | 核心作用 | 关键字段 |
|---|---|---|
| sysindexes | 存储每个索引的底层信息,包括索引名、类型、树层数、页数、列信息等 | idxname, tabid, idxtype, clustered, levels, nleaf, npages, part1~part16, indexkeys |
| sysindices | 基于sysindexes等表创建的视图,查起来更直观,直接显示表名和索引名 | idxname, owner, tabname, idxtype, levels, nleaf, nunique, part1~part16 |
| syscolumns | 存储表的字段信息,用于关联出索引在哪些列上 | tabid, colno, colname, coltype |
| systables | 存储表的基本信息,用于把tabid转成表名 | tabid, tabname, owner |
| sysconstraints | 存储主键、唯一约束、外键、检查约束,记录约束与索引的关系 | constrtype, idxname, tabid, constrname |
这里特别说明一下sysindices。它不是一张物理表,而是一个视图,内部已经把sysindexes和systables、syscolumns连接好了,所以你直接查sysindices就能看到“哪个表、哪个索引、哪些列”这种偏业务的信息。普通日常查询,用sysindices就够了;如果要做深入的性能和空间分析,再回到sysindexes查原始字段。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 查询索引的核心SQL写法,直接抄作业
2.1 最简单的方式:直接查sysindices视图
如果你只是想快速看看当前数据库里有哪些索引,最简单粗暴的写法就是:
sql复制SELECT * FROM sysindices;
这个查询会把当前库所有表和索引的对应关系列出来。不过实际生产环境不建议这么裸查,因为数据量一大,全表扫sysindices会非常慢,而且输出列太多,看着也累。
建议给它加上过滤条件。比如我只想看某个用户下所有表的索引:
sql复制SELECT
tabname,
idxname,
idxtype,
clustered,
levels,
nleaf,
nunique
FROM sysindices
WHERE owner = 'informix'
ORDER BY tabname, idxname;
owner字段在sysindices视图里就是表所有者的名字,如果你用的是informix超级用户,它一般是informix。这个查询结果很干净,表名、索引名、类型、是否聚集索引、B+树层数、叶子节点数、唯一值个数全都有了。其中nleaf和nunique是判断索引规模和选择性的好指标,后文会细说。
2.2 查询指定表上的索引和索引列
只看表名和索引名还不够,很多时候你要知道索引到底建在哪些列上。这时候就要关联syscolumns了。GBase 8s在sysindexes里用part1到part16这16个字段存储索引列的位置信息,字段值是列在表中的序号,也就是syscolumns.colno。所以查询可以这么写:
sql复制SELECT
t.tabname,
i.idxname,
i.idxtype,
c.colname
FROM sysindexes i
JOIN systables t ON i.tabid = t.tabid
JOIN syscolumns c ON c.tabid = i.tabid
WHERE t.tabname = 'orders'
AND (
i.part1 = c.colno OR i.part2 = c.colno OR i.part3 = c.colno
OR i.part4 = c.colno OR i.part5 = c.colno OR i.part6 = c.colno
OR i.part7 = c.colno OR i.part8 = c.colno OR i.part9 = c.colno
OR i.part10 = c.colno OR i.part11 = c.colno OR i.part12 = c.colno
OR i.part13 = c.colno OR i.part14 = c.colno OR i.part15 = c.colno
OR i.part16 = c.colno
)
ORDER BY t.tabname, i.idxname, c.colno;
这个SQL就是典型的“索引列展开”。part1对应第一个索引列,part2对应第二个索引列,依此类推。为什么用OR而不是直接JOIN?因为一个索引可能有多个列,其中任何一个列都可能是c.colno,所以用OR把所有位置都匹配一遍。
不过这个写法有个局限——它只能处理少于16列的索引。如果索引列超过16个,part1到part16就不够用了,系统会把列的详细信息压缩存在indexkeys字段里,后面第3章我会专门讲indexkeys怎么理解。
2.3 查询主键和唯一约束对应的索引
业务上经常需要确认某张表的主键到底叫什么索引名,这在做迁移或者判断约束是否会阻塞DDL的时候特别有用。GBase 8s中主键、唯一约束本质上都会创建一个对应的索引,这个对应关系存在sysconstraints里。
sysconstraints的constrtype字段有几种取值:P代表主键,U代表唯一约束,C代表检查约束,R代表外键引用约束。所以你查主键索引可以这么写:
sql复制SELECT
t.tabname,
k.constrname AS pk_name,
i.idxname AS index_name,
i.idxtype
FROM sysconstraints k
JOIN systables t ON k.tabid = t.tabid
JOIN sysindexes i ON k.idxname = i.idxname
WHERE k.constrtype = 'P'
AND t.tabname = 'orders';
这里关键点是sysconstraints里的idxname字段直接对应sysindexes里的idxname,通过它就能把约束和索引串起来。同理,如果你想查唯一约束对应的索引,把constrtype改成'U'就行了。
2.4 批量巡检所有索引的空间占用
有些场景下,比如做季度巡检,你需要快速掌握整个库索引的规模,判断哪些索引需要重建或清理。我习惯把索引页数、叶子节点数、层级一起查出来,形成一份巡检报告:
sql复制SELECT
t.tabname,
i.idxname,
i.idxtype,
i.levels,
i.nleaf,
i.nunique,
i.npages,
CASE
WHEN i.clustered = 'C' THEN 'CLUSTERED'
ELSE 'NOT CLUSTERED'
END AS cluster_flag
FROM sysindexes i
JOIN systables t ON i.tabid = t.tabid
WHERE t.tabtype = 'T'
ORDER BY i.nleaf DESC;
tabtype='T'表示普通表,把视图、临时表这类过滤掉。nleaf是叶子节点个数,nunique是索引中唯一键值的个数。如果一张大表索引的nunique很小,说明索引区分度不高,优化器大概率不会走这个索引。这个查询结果配合后面的执行计划分析,基本能定位一大半的慢查询问题。
3. 读懂sysindexes的关键字段,才能不采坑
3.1 idxtype、clustered、levels分别代表什么
sysindexes里有些字段光看名字你是猜不准含义的,这里挑几个高频使用的字段详细讲。
idxtype字段表示索引类型。在GBase 8s中,最常见的取值是U、T、D等,U代表唯一索引,T代表普通非唯一索引,D代表降序索引,具体不同版本可能会有差异,实际以官方系统目录说明为准。我平时查索引时习惯无条件把idxtype带出来,就是为了快速判断这个索引到底是不是唯一的。
clustered字段表示索引是否是聚集索引。聚集索引的意思是表中数据的物理顺序尽量和索引键顺序保持一致,通常一个表只能有一个聚集索引。在GBase 8s里这个字段的取值一般是C或N,C代表聚集索引,N代表非聚集。聚集索引在范围查询场景下性能会有明显提升,因为相邻键值的数据页在物理上也相邻,IO次数会少很多。
levels字段代表B+树的层数。层数越高,意味着查询一个键值需要访问的索引页就越多。对于一个三层B+树,通常已经能支撑百万甚至千万级数据量;如果一张几百万行的表,索引levels跑到5层以上,那就要小心了,可能索引结构已经不太健康,考虑重建索引。
3.2 nleaf、nunique、npages看索引健康度
nleaf是叶子节点的个数,nunique是索引中唯一键的个数,npages是索引占用的页数。
这三个字段组合起来能看出很多问题。比如nleaf特别大但nunique很小,说明索引的选择性差,重复键多,这种索引往往对查询帮助不大,还拖累写入性能,可以考虑删除。又比如npages比nleaf大出好几倍,可能是索引碎片比较多,页利用率不高。
我自己的习惯是写一个简单的比例计算:
sql复制SELECT
idxname,
nleaf,
nunique,
npages,
CASE
WHEN nunique = 0 THEN 0
ELSE ROUND(nleaf * 1.0 / nunique, 2)
END AS avg_leaf_per_key
FROM sysindexes
ORDER BY avg_leaf_per_key DESC;
avg_leaf_per_key的值如果很大,说明平均一个键值对应了太多叶子页,索引维护成本高、查询效果差。这个不是官方指标,纯粹是个人排查经验的总结,但实际用起来很能说明问题。
3.3 part1~part16和indexkeys的关系
这是GBase 8s查索引时最容易懵的地方。part1到part16字段存储的是索引列对应的列序号,但是它们只覆盖前16列。如果索引列超过16个,或者索引中包含函数运算、字符运算等特殊定义,part字段就无法完整表示了,系统会把压缩后的索引键信息放到indexkeys字段。
indexkeys字段是一个二进制或者字节数组类型的存储结构,官方文档一般把它描述成内部表示格式,不建议直接手工解析。记得我第一次尝试去读它的时候,对着字节数翻来覆去对不上,后来干脆放弃,改用sysindices视图加应用层脚本来解析。
如果你确实需要查看一个多列索引的完整定义,更靠谱的做法还是关联part字段,或者直接在dbaccess里执行建索引的DDL反向导出。GBase 8s没有直接提供类似MySQL的SHOW CREATE INDEX命令,但你可以通过查询系统表拼接出索引定义。遇到部分索引或函数索引时,如果发现part1全是0,说明这里不是简单列索引,需要回到应用层或者官方工具去解析。
3.4 为什么不要直接修改系统目录表
这里必须提醒一句:sysindexes、sysindices这些系统目录表,查询随便查,但绝对不要用UPDATE、DELETE去改。系统目录表是数据库内核维护的对象,你手工去改索引元数据,轻则导致优化器判断错误,重则直接把数据库搞崩。网上有些“优化教程”教你直接update sysindexes调整统计信息,这种操作在GBase 8s里是绝对禁止的。正确的做法是通过UPDATE STATISTICS命令重新收集统计信息,后文会详细讲。
4. 用dbaccess和onstat快速巡检索引
4.1 dbaccess里执行索引查询
GBase 8s自带的交互工具是dbaccess,相信大家都不陌生。要执行索引查询,直接进dbaccess操作:
bash复制dbaccess your_database
进入交互界面后,输入SQL再按Ctrl+D或者输入“;”执行。也可以用管道把SQL直接喂给dbaccess:
bash复制echo "SELECT tabname, idxname, idxtype FROM sysindices WHERE owner = 'informix';" | dbaccess your_database
写脚本的时候这个用法很方便,可以配合crontab做定时巡检。不过记得dbaccess的交互行为和MySQL的mysql客户端不太一样,SQL末尾要加分号,如果写了多条SQL,每条都要用分号结束,否则它不会执行。
4.2 onstat -D观察索引页分布
如果你已经不只是想“查”索引,而是想看索引在磁盘上的分布情况,那就得上onstat命令了。onstat是GBase 8s自带的系统监控工具,类似Oracle的gv$视图加操作系统级别的状态工具,不需要登录数据库就能执行。
onstat -D输出的是每个tblspace中数据页、索引页、剩余页的统计信息。执行方式很简单:
bash复制onstat -D
输出里会有类似这样几列:tblspace名称、partnum、pagesize、data page数、index page数、remainder page数、btree page数等。当你想判断某个表上的索引是不是碎片太多,重点看index page数和data page数的比例。如果一张表的数据只有几十MB,索引页却占了好几百MB,这多半就是索引碎片或者索引设计有问题了。
onstat -D的输出比较长,你可以用grep过滤:
bash复制onstat -D | grep "orders"
这样能快速找到指定表相关的部分。
4.3 onstat -d查看空间和剩余页
onstat -d和onstat -D虽然只差一个字母,但作用完全不同。onstat -d显示的是dbspace、chunk、free list这些存储空间信息,属于数据库空间的整体视图。它对你理解索引的物理存储位置有一定帮助,但并不会直接列出索引明细。
实际巡检时我的习惯是:先用onstat -d确认数据库空间整体是否健康,有没有chunk只读、空间满的情况;再用onstat -D定位特定表或者索引的页分布;最后回到dbaccess里用系统表SQL查索引的元数据。三步走下来,索引的健康度基本心里有数。
4.4 把索引巡检封装成脚本
日常巡检不可能每次都手动敲命令,我会把常用的索引查询封装成一个shell脚本,放到监控机上,每天早上自动跑一遍。脚本逻辑很简单:
bash复制#!/bin/bash
export INFORMIXSERVER=your_server_name
export INFORMIXDIR=/opt/gbase8s
export PATH=$INFORMIXDIR/bin:$PATH
dbaccess your_database <<EOF
SELECT
t.tabname,
i.idxname,
i.idxtype,
i.levels,
i.nleaf,
i.nunique,
i.npages
FROM sysindexes i
JOIN systables t ON i.tabid = t.tabid
WHERE t.tabtype = 'T'
AND i.nleaf > 10000
ORDER BY i.nleaf DESC;
EOF
加一个nleaf>10000的过滤条件,把大索引捞出来。每次跑完把结果重定向到日志文件里,对比一下叶子节点数的增长趋势,能及时发现一些异常的索引膨胀。这个思路在几十套GBase 8s实例的维护场景下非常实用。
5. 索引有没有被用上,执行计划才是最终答案
5.1 SET EXPLAIN ON快速定位
索引建是建了,但优化器到底用没用,这才是查询索引命令要解决的终极问题。GBase 8s里看执行计划的命令是SET EXPLAIN ON,执行后SQL执行计划会写入当前目录下的sqexplain.out文件。
具体操作流程是在dbaccess里输入:
sql复制SET EXPLAIN ON;
SELECT * FROM orders WHERE order_date >= '2025-01-01';
SET EXPLAIN OFF;
然后查看sqexplain.out文件:
bash复制cat sqexplain.out
文件里会有一段类似这样的描述:Estimated Cost、Index Key、Query Plan等。重点看是否出现了索引名。如果计划里出现你新建的索引名,说明优化器选择了这个索引;如果显示的是SEQSCAN全表扫描,那索引可能没被用上。
5.2 解读执行计划里的索引关键字
GBase 8s的执行计划文本和Oracle、MySQL差别比较大。它不会直接画出什么树形图,而是用缩进和关键字描述扫描方式。常见的关键字有:
- SEQSCAN:顺序扫描,也就是全表扫描。
- INDEX PATH:索引扫描,后面会跟着索引名和索引键。
- INDEX KEY:表示索引键值范围。
- FILTER:过滤条件。
举个例子,如果执行计划里出现:
text复制INDEX PATH
Index Name: idx_orders_date
Index Keys: order_date
这就说明优化器用上了idx_orders_date这个索引。如果只是出现SEQSCAN,就要怀疑为什么索引没被选中。
5.3 常见走不上索引的原因
根据我自己的排查经验,索引建了但用不上,常见原因有这几种。
第一,查询条件对索引列做了函数运算。比如索引列是order_date,查询写了WHERE TO_CHAR(order_date, 'YYYY-MM-DD') = '2025-01-01',索引就废了,因为索引里存的是原始值,不是函数处理后的值。这种情况要改成条件直接作用在原始列上,或者建函数索引。
第二,索引区分度太低。优化器发现全表扫描比走索引更快,自然不选索引。比如性别列、状态列这种只有几个枚举值的列,建了索引也大概率不走。
第三,统计信息太旧。GBase 8s的优化器依赖系统目录里的统计信息做成本估算。如果表数据量已经翻了好几倍,但UPDATE STATISTICS一直没执行过,优化器对行数和分布情况判断严重失真,就可能弃用索引。这种情况重跑一遍统计信息往往立竿见影。
第四,前导列不在查询条件里。复合索引idx_a_b_c的查询条件如果只写了c列,没有a列,很多情况下优化器无法触发完整索引扫描。这个属于最经典的复合索引失效问题。
6. 索引维护实操:重建、改名和统计信息
6.1 用ALTER INDEX REBUILD重建索引
当我们确认索引碎片严重,或者发现levels层级异常时,就需要重建索引。GBase 8s支持在线重建索引,语法是:
sql复制ALTER INDEX idx_orders_date REBUILD;
这条命令会重建索引并释放旧的索引空间。它的好处是尽量不影响在线业务,相比DROP INDEX加CREATE INDEX的方式,锁表现象会好很多。不过在线重建不等于完全无锁,它仍然可能对并发写入有一定影响,建议在业务低峰期操作。
重建时可以配合FILLFACTOR参数控制页填充率。比如:
sql复制ALTER INDEX idx_orders_date REBUILD FILLFACTOR 90;
FILLFACTOR表示索引页的填充比例,90表示每个叶节点预留下10%的空间给后续插入的键值。如果一张表写入频繁,建议把FILLFACTOR设得低一点;如果是只读表,可以设到100,空间利用率更高。
6.2 离线重建索引的适用场景
ALTER INDEX REBUILD虽然好,但某些场景下还是得走离线重建。比如索引结构本身已经损坏,或者你想把一个普通索引改成聚集索引,这时ALTER INDEX不一定满足要求。
GBase 8s里把索引改为聚集索引的命令是:
sql复制ALTER INDEX idx_orders_date TO CLUSTER;
取消聚集索引:
sql复制ALTER INDEX idx_orders_date TO NOT CLUSTER;
如果要做彻底的离线重建,最朴素也最可靠的方式还是:
sql复制DROP INDEX idx_orders_date;
CREATE INDEX idx_orders_date ON orders(order_date);
执行DROP INDEX的时候,要确认这个索引没有被主键、唯一约束引用,否则会报错。这也是为什么前面强调要先查sysconstraints,搞清楚约束和索引的对应关系。
6.3 RENAME INDEX重命名索引
给索引改名也是运维中经常遇到的需求,尤其是接手老库发现索引命名乱七八糟的时候。GBase 8s里重命名索引的命令是RENAME INDEX,用法和ALTER TABLE RENAME类似:
sql复制RENAME INDEX idx_orders_date TO idx_orders_create_time;
需要注意的是,如果这个索引是主键或者唯一约束的底层索引,单纯改索引名可能不够,还要确认约束名是否也要同步调整,否则应用日志里通过约束名定位问题的时候会对不上。
6.4 UPDATE STATISTICS不能忽视
最后要讲讲统计信息。很多索引问题其实不是索引本身的问题,而是统计信息不准的问题。GBase 8s里收集统计信息的命令是UPDATE STATISTICS,基础用法:
sql复制UPDATE STATISTICS FOR TABLE orders;
这个命令会收集表级的行数、页数等信息。如果表比较大,可以指定中等级别:
sql复制UPDATE STATISTICS MEDIUM FOR TABLE orders;
MEDIUM级别会做采样统计,比LOW准,又比HIGH快,适合大多数业务表。HIGH级别最准,但耗时也最长,大表一跑可能就是几十分钟甚至几小时,慎用。
我自己的做法是:大表每周做一次MEDIUM级别的统计信息更新,小表每天或者每天都做一次LOW级别更新。统计信息更新完,再回头用SET EXPLAIN ON看执行计划,很多原来不走索引的SQL自动就恢复正常了。
再补充一个经验:每次在大表上做完大量DML操作,比如批量导入、按月归档、清理历史数据,都要立刻跑一下UPDATE STATISTICS。GBase 8s的优化器不会在DML之后自动实时更新统计信息,如果不手动触发,表数据量变化巨大时,执行计划会非常离谱。
索引的查询和维护这事,说难不难,说简单也不简单。难的是很多命令和字段第一次见确实反直觉,尤其从MySQL或者Oracle转过来的同学,容易被GBase 8s这套系统表结构搞得一头雾水。但只要把sysindexes、sysindices、syscolumns的关系理清楚,再配合onstat和EXPLAIN这些工具,日常索引巡检就没什么障碍了。我个人做了这么久GBase 8s运维,最大的体会就是:索引查询不是只为了看“有没有建索引”,而是要通过索引元数据反向推理业务压力和数据特征。下次再遇到慢查询,别急着改SQL,先花几分钟查查索引状态,往往答案就在sysindexes里。
