接手一套新的数据库环境,我习惯先把命令行客户端伺候明白。GBase 8c 的 gsql 是日常打交道最多的入口,而它的元命令——也就是那些以反斜杠开头、看起来像"草稿纸快捷键"的指令——恰恰是很多人在入门时最容易忽略、真正用熟了又回不去的那部分。这篇不打算照抄官方手册,我把平时在巡检、排查、写脚本时真正用得上的元命令按场景整理出来,顺便把容易踩的坑也一块说了。
适合谁来读?刚接触 GBase 8c、还在用 DBeaver 或 Navicat 点鼠标的同学,以及已经从其他数据库转过来、想尽快把命令行效率提上去的运维和开发。元命令这东西谈不上高深,但每记住一个,日常操作就能少敲一遍 SQL、少切一次工具窗口,属于投入产出比极高的一类知识。
1. 连接与全局信息:先回答"我在哪、连的是谁、库里有什么"
很多问题排查到最后,发现根因是连错了库、看错了 schema、甚至用错了连接端口。gsql 提供了一批元命令,专门用来快速确认当前会话的上下文信息,这些命令应该成为你每次登录后的"肌肉记忆"。
1.1 \l、\conninfo:登录后先做的两件事
第一条值得记住的是\l,作用是列出当前实例上所有可见的数据库。和直接查pg_database系统表相比,\l的输出更干净,还带着库的属主、编码、表空间等常用信息。我习惯在登录后先敲一次\l,确认自己没有连错实例,尤其是 GBase 8c 这种分布式架构下,多个节点、多个数据库并存,手滑连错环境是常有的事。
第二条是\conninfo,它会打印当前连接的基本信息:你连的是哪个数据库、以什么用户身份登录、连接走的是 TCP 还是本地 socket、端口是多少。这个命令在排查网络问题和服务端日志时特别好用。比如一段 SQL 执行很慢,你需要确认慢在哪个节点上,第一步就是通过\conninfo确认当前会话到底落在哪台协调节点上。
\conninfo输出会包含类似"当前连接的数据库、用户、连接方式、端口"这样一组信息,一眼就能定位。我写自动化巡检脚本时,也会在脚本开头先执行一次\conninfo并连同时间戳一起落盘,这样事后对日志,能明确知道某段输出是在哪个环境、哪个用户下产生的。
1.2 \dn、\du、\db、\encoding:别等查错才想起来
接着是一组"对象目录"命令。\dn列出当前数据库里的所有 schema,\dn+还会带上属主和权限描述。在 GBase 8c 里,一个数据库下通常有多个业务 schema,如果不先确认 schema 就叫查表,很容易陷入"表明明存在却查不到"的困惑。
\du列出用户和角色。这个命令在权限排查时非常关键。比如某个应用账户报没有权限,我先\du看角色归属,再结合\du+看属性,基本就能定位是继承关系缺失还是直接授权没给。\db列出表空间,配合后续的表大小查询,可以快速判断数据是否被均匀分布到了预期位置。
还有一个很容易被忽略的命令是\encoding。它会显示当前客户端的字符集编码,后面带参数可以直接切换,比如\encoding UTF8。如果你在 gsql 里执行中文相关 SQL 时出现乱码,第一步不是怀疑数据,而是先看服务端编码和客户端编码是否一致。这个坑在国产数据库环境里尤其常见,因为历史库可能用的编码是 GBK 或 GB18030,而你的终端默认是 UTF8。两者不一致,查出来的中文自然是一堆问号。
\encoding GB18030切换之后再看数据,乱码问题通常当场消失。另外,\q退出 gsql、\?查看全部元命令帮助、\h查看 SQL 语法帮助,这三个属于"保命命令"。遇到记不清的元命令参数时,直接\?比翻文档快得多。
2. 表结构查询:把 \d 系列用出效率
\d系列是 gsql 里使用频率最高的一组元命令,我敢说很多人天天敲\d,但并没有把它用全、用透。这个系列涵盖表、视图、索引、序列、函数、类型等几乎所有对象类型的结构查询,熟练掌握之后,你基本可以告别"打开图形工具看表结构"的习惯。
2.1 \dt 与搜索路径:为什么总提示找不到表
\dt列出当前数据库里所有用户表,\dt+会额外显示表大小、描述等详情。很多人在这里遇到的第一个问题是:明明\dt能列出表,但\dt 表名却提示找不到关系。这通常不是表不存在,而是搜索路径(search_path)里没有包含该表所在的 schema。
gsql 的\d系列命令在解析参数时,会依赖当前会话的search_path。如果你在多个 schema 下都有同名表,不带 schema 前缀的\dt 表名很可能查到了别的 schema 里的同名表,或者干脆找不到。解决办法有两个:
-- 方式一:显式指定 schema \d public.t1 -- 方式二:先切 search_path,再查 SET search_path TO myschema, public; \d t1我建议日常操作养成显式带 schema 的习惯,因为\dt public.t1这样写,输出里也会清楚标注表所属的 schema,不会产生歧义。另外,\dt支持通配符模式匹配,比如\dt *tmp*可以匹配所有名字里带 tmp 的表。这个技巧在批量清理临时表时非常好使,先用通配符查出来锁定的对象,再决定后续处理。
2.2 \d 与 \d+:结构信息里藏着哪些排障线索
不带任何后缀的\d 表名是最常用的结构查看命令,它会列出表的列名、数据类型、是否允许为空、默认值、以及列的注释信息。相比直接查询information_schema.columns,\d的可读性要好得多,尤其是字段多的时候,对齐输出一眼就能扫完。
\d+ 表名则是在结构基础上追加更多物理信息:表所在表空间、访问方式、fillfactor、依赖的索引、行数估算等。这些信息在排障时很有用。举个例子,我曾经查一张业务大表的存储参数,发现 fillfactor 被设置得很低,导致表膨胀得厉害,这就是通过\d+看到的线索,配合后续的VACUUM FULL才把空间收回来。
\d+ orders另外要注意,\d系列显示的是结构快照,它的底层仍然是查询系统表。如果你需要精确判断某列的类型长度、默认值生成逻辑、或者依赖关系,\d提供的可视化信息可能不够,最终还是得查pg_attribute、pg_class这类系统表。元命令的优势在于"快速给人看",SQL 的优势在于"灵活给程序用",两者并不冲突,看场景选择即可。
2.3 视图、函数、序列与类型:不常用的也要会用
除了表,\d系列还有很多变体:
\dv:列出视图\di:列出索引\ds:列出序列\df:列出函数\dT:列出数据类型\dp或\z:显示表的权限
其中\df和\sf是我在排查存储过程性能问题时离不开的组合。\df可以带模式过滤,比如\df *proc*只列出名称里带 proc 的函数。找到目标函数后,直接\sf 函数名就能看到函数的完整定义体,连去翻建脚本的功夫都省了。
\sf public.calc_monthly_revenue\dT列出数据库里的数据类型,在确认某些自定义类型是否存在时很管用。比如业务方反馈说某字段类型在测试环境能建、生产环境建不了,我先\dT对比两边环境,通常几分钟就能发现是自定义类型缺失,而不是权限问题。
这一节的要点就一句话:\d是个动词,后面接不同的对象类型字母,就能看不同的东西。记不住全貌没关系,记住\d 表名看结构、\dt列表、\df列函数、\sf看函数定义这四个高频用法,日常 80% 的结构查询需求都能覆盖。
3. 输出格式与结果落盘:\x、\a、\t、\o 的配合
gsql 元命令里最容易被低估的,是输出格式控制这一组。很多人用 gsql 只停留在"敲 SQL、看结果"的层面,完全没想到结果还可以按需排版、导出、落盘、甚至变成机器可解析的格式。这一节讲清楚,你的 gsql 就从"查询工具"升级成"轻量数据导出工具"了。
3.1 屏幕可读性:\x 与 \pset
先解决"屏幕上看不清"的问题。当一条 SQL 返回的列特别多时,默认的表格对齐模式会把每行数据拉得很长,折行之后根本没法看。这时候用\x开启扩展显示模式,每一行记录会变成类似 KV 的纵向排列格式,字段名和值一一对应,多列结果看起来非常舒服。
\x SELECT * FROM pg_settings WHERE name LIKE '%work_mem%';再执行同样的 SQL,输出会从横表变成纵表,这在大字段多列查询时简直是救命的。需要恢复正常显示时,再执行一次\x即可,它是开关式的。\x在查看函数定义、表结构、或者系统配置参数这类列数很多的场景里,使用率极高。
\pset则是更精细的控制工具,可以设置很多输出参数,比如空值显示方式、字段分隔符、表格边框样式等。不带参数执行\pset会列出当前所有输出设置项,方便你随时确认状态。我常用的是\pset null 'NULL',把查询结果里的空值显示成明确的字符串,避免肉眼分不清"真空"和"字符串空"。
3.2 给程序用的输出:\a、\t、\o
人看的输出追求可读性,程序消费的输出则追求可解析性。\a在对齐模式和非对齐模式之间切换。对齐模式下输出带边框和填充空格,适合人看;非对齐模式下字段之间用分隔符隔开,适合后续用 awk、sed、Excel 这些工具处理。
\t的作用是只输出查询结果行,去掉列名头部和行数统计尾部。这两行统计信息人看没问题,但程序按行解析时会成为"脏数据"。所以"\a+\t"组合广为人知,用来生成干净的纯数据文本。
\o则是把查询结果重定向到文件。用法是:
\o /tmp/orders_export.txt SELECT * FROM orders WHERE order_date >= '2024-01-01'; \o注意第二次\o不带路径,作用是把输出恢复回屏幕。初学者最容易犯的错是开了\o忘记关,然后后续所有查询结果都悄无声息地进了同一个文件,自己在屏幕上还不知情。我建议在脚本里写完\o 文件路径后,紧接着就写好恢复用的\o,两者成对出现,养成习惯就不会漏。
3.3 写文件的坑:路径、恢复与字符集
关于\o有个非常关键的认知:\o写的是运行 gsql 的客户端所在机器的文件,不是数据库服务器上的文件。也就是说,如果你在个人电脑上通过 gsql 连到远端数据库,\o /tmp/xxx.txt保存的文件不在数据库服务器上,而在你的电脑上。这个误解在第一次看到服务器上没生成文件时往往让人抓狂,其实只是对"谁在执行 gsql"这个概念没厘清。换句话说:\o是客户端工具的行为,跟数据库服务端没有关系。
类似的还有\i读取脚本文件,也是读取客户端本地文件。如果要在服务器端做文件操作,那就不能靠 gsql 的重定向,得用服务端工具或系统命令。理解了这一点,再配合前面说的\a和\t,一个常用的导出 CSV 方案就出来了:
\a \t \pset fieldsep ',' \o /tmp/orders.csv SELECT order_id, customer_id, amount FROM orders WHERE order_date >= '2024-01-01'; \o \t \a\pset fieldsep ','把字段分隔符改成逗号,加上非对齐、无列名的组合,生成的文本就是一种朴素的 CSV。用 Excel 直接打开时注意中文编码,如果乱码,用\encoding先把客户端编码切到目标编码再导。我自己导数据给业务方时,通常统一导 UTF8 编码的 CSV,几乎没再出过乱码问题。
4. 脚本化与变量替换:让重复劳动"一键化"
元命令的另一大价值在于脚本化。你不需要记住所有 SQL 再一条条复制粘贴,把常用操作攒成 .sql 脚本文件,配合变量替换、条件执行和批量执行,日常那些重复性工作就能压缩成一条命令的事。
4.1 \i、\echo、\o:脚本的基本骨架
\i 文件路径是执行外部脚本文件的命令。比如你有一段初始化 SQL,包含建表、建索引、造数据,把它存在 init.sql 里,在 gsql 里直接\i /path/to/init.sql就能整段执行。脚本里的每条语句和元命令都会按顺序执行,执行结果照样显示在屏幕上。
执行脚本时如果想输出一些提示信息,可以用\echo。\echo '开始执行初始化脚本'会在屏幕上打印一行文本,脚本里分阶段打印提示,跑批时日志看起来层次分明。\qecho和\echo类似,但它是把内容输出到\o指定的文件里,用于在导出文件里写注释非常合适。
如果脚本执行中出错,默认行为是继续执行后续语句。这个默认行为有时候很坑:前半段写好了,中间一条报错,后半段继续跑,等你看日志时都不知道哪一步出错了。解决办法是脚本开头设置:
\set ON_ERROR_STOP on这样只要有一条 SQL 执行失败,脚本立即中断,错误被迅速暴露。这个设置在初始化、迁移、批量变更场景里尤其重要,能让"半成功"状态变成"要么全做要么全停"的明确状态。
4.2 \set 与变量替换:冒号前后的门道
\set用来定义 gsql 变量。变量最常用的场景是在脚本里做参数化,避免手工改动脚本内容。比如:
\set tname t1 SELECT * FROM :tname;这里:tname执行时会被替换成变量值 t1,实际执行的是SELECT * FROM t1;。如果在脚本里改成\set tname t2,同样的语句就变成查 t2。这个机制就是 gsql 的变量替换,非常适合做"同一套脚本跑不同参数"的场景。
变量替换有个关键区别要注意::var是直接替换值,而:'var'会把值加上单引号再替换。后者的典型用途是拼接字符串条件:
\set uname '张三' SELECT * FROM users WHERE name = :'uname';实际执行时等价于SELECT * FROM users WHERE name = '张三';。如果不加引号,直接用:uname,替换后就变成WHERE name = 张三,多半会报语法错误。记住这个规则:值要作为字符串字面量使用时,用:'var';值要作为标识符(如表名、列名)使用时,用:var。两者别搞混。
4.3 \gexec:动态批量操作的正确姿势
\gexec是我个人最喜欢的一个元命令,它能将上一条查询返回的每一行结果当作 SQL 语句去执行。这听着有点绕,实际用起来却极其实用。典型场景是批量授权:
SELECT 'GRANT SELECT ON ' || tablename || ' TO public;' FROM pg_tables WHERE schemaname = 'public'; \gexec这条语句先通过查询生成若干条 GRANT 语句,然后\gexec把这些语句逐条执行掉。效果相当于你用一段动态 SQL 完成了批量授权,省去写存储过程或循环脚本的麻烦。类似的场景还有批量 truncate、批量更新统计信息、批量建视图等。
用\gexec之前,建议先用不带\gexec的普通查询把生成的 SQL 过目一遍,确认无误再接上\gexec执行。我之前有一次批量操作就是这么干的:先跑一遍查询,看到生成的语句里有几条目标不太对,赶紧停下来调整条件,避免直接执行造成不可逆的操作。\gexec威力大,但用之前务必先"预览"一遍。
5. 排障与巡检场景:元命令的组合打法
元命令单个用只能省点打字时间,真正的高价值场景是把它们组合起来,构成一套排查和巡检的打法。尤其是面对慢查询、权限异常、空间膨胀这类常见问题,合理的元命令组合能让定位速度提升一个量级。
5.1 \timing 与执行耗时定位
\timing是打开/关闭 SQL 执行计时功能的开关。开启后,每执行一条 SQL,gsql 都会在结果尾部打印实际耗时。这个命令是我排查慢查询的第一步。
\timing on SELECT count(*) FROM big_table;输出尾部会显示类似 "Time: 1234.567 ms" 的时间信息。注意这里显示的耗时包含了客户端到服务端的往返时间,在分布式架构下,还包含协调节点分发计划和汇总结果的时间。如果你发现某条 SQL 耗时异常,可以先开\timing复现,再决定下一步用EXPLAIN ANALYZE看执行计划,还是从资源占用角度排查。\timing只是个计时器,本身不改变执行行为,但它给后续所有排查提供了一个"基准线"。
5.2 函数、索引与表大小:慢查询排查三板斧
定位到慢 SQL 后,如果怀疑是函数内部逻辑导致,用\sf 函数名查看函数定义,配合\timing on单独执行函数做基准测试,很快能确认瓶颈是否在函数体本身。如果怀疑是索引失效,用\di+ 表名查看索引详情,重点关注索引大小、索引所属表和可见性。索引膨胀严重的话,查询优化器可能放弃走索引扫描。
表空间膨胀是另一个高频问题。用\dt+ 表名看到表的大小后,再配合 SQL 查询表的总大小和索引大小,就能判断是数据膨胀还是索引膨胀。通常我会用这条 SQL 快速定位库内占用最大的表:
SELECT schemaname || '.' || tablename AS tbl, pg_size_pretty(pg_total_relation_size(schemaname || '.' || tablename)) AS total_size FROM pg_tables WHERE schemaname = 'public' ORDER BY pg_total_relation_size(schemaname || '.' || tablename) DESC LIMIT 10;再加上\timing on查看各表统计信息的执行耗时,一轮巡检下来,哪些表需要清理、哪些索引需要重建、哪些 SQL 需要优化,基本心中有数。
5.3 一个可落地的巡检脚本组合
把这些元命令串成一个简单巡检脚本,效果立竿见影。脚本思路:先记录会话连接信息、打开计时、切换非对齐输出模式、把结果落盘,然后按模块执行若干查询,最后恢复屏幕输出、关闭计时。
-- 巡检脚本片段:report_check.sql \conninfo \timing on \a \t \o /tmp/gbase_check_$(date +%Y%m%d).txt SELECT '会话开始时间: ' || now()::text; SELECT '当前数据库: ' || current_database(); SELECT '数据库版本: ' || version(); SELECT '连接数使用情况: ' || count(*) FROM pg_stat_activity; SELECT '占用空间前10的表:'; SELECT schemaname || '.' || tablename AS tbl, pg_size_pretty(pg_total_relation_size(schemaname || '.' || tablename)) AS total_size FROM pg_tables WHERE schemaname = 'public' ORDER BY pg_total_relation_size(schemaname || '.' || tablename) DESC LIMIT 10; \o \t \a \timing off执行这个脚本时,注意\o路径中的$(date +%Y%m%d)是 shell 变量,需要由 shell 展开,所以要在 bash 里用 gsql 执行,而不是在 gsql 内部直接敲。具体调用方式是在操作系统命令行里:
gsql -d postgres -f /path/to/report_check.sql如果 gsql 不支持-f参数,或者你更习惯在 gsql 会话里执行,那就把文件路径放到\i后面:
\i /path/to/report_check.sql无论哪种方式,核心思路都是一样的:预先用元命令设计好输出格式和落盘位置,然后按下一次回车,一份带时间戳、带基础环境信息、带资源占用的巡检报告就自动生成了。我第一次跑通这个组合时,最大的感受是"原来日常巡检真的可以一边喝咖啡一边完成"。
最后再说一个习惯层面的心得:元命令记不住不要硬背,高频的记住几个,低频的用到时\?现查就行。比起背命令列表,更值得养成的是"先想清楚自己要人看还是给程序用、要屏幕输出还是落盘、要交互还是脚本"这三个习惯。想清楚之后再选元命令,你会发现 gsql 的灵活性比想象中大得多。