开发过程中有些细节容易被忽略,今天挑几个重点聊一聊。
用了十来年数据库,我发现一个挺普遍的现象:很多人把 SQL 编辑器当成高级记事本来用。打开工具,写一句 select * from 某张表,看一眼结果,关掉。语法高亮、自动补全、执行计划、数据编辑器这些功能,一个都没碰过。
说实话我以前也这样。直到有一次赶版本,我在编辑器里手敲了一条 update 语句,少写了个 where,一张二十多万行的配置表被整表刷了一遍。那天晚上我一边恢复数据一边想,这事儿但凡我提前用一下编辑器的事务按钮,都不至于狼狈成那样。
从那以后,我对 SQL 编辑器这个东西的态度就变了。这篇来聊聊我在金仓数据库生态里用得最多的两个工具——KStudio 和 KSQL Developer,顺便把建表、增、删、改、查这一整套代码案例完整过一遍。都是实操过的,不是纸上谈兵。
一、先把两个工具掰扯清楚
这俩名字老被人搞混,我每次在技术群里被问到都要解释一遍。
KStudio 是跟着 KingbaseES 一起装的老牌选手,Windows 上一般在 $InstallDir/ClientTools/guitools/KStudio 目录下,双击 KStudio.exe 就能起;Linux 上命令行进到同一个目录执行 ./KStudio。它是 Eclipse RCP 那一路的风格,菜单栏是「文件 / 编辑 / 导航 / 查找 / SQL编辑器 / 数据库 / 窗口 / 帮助」,左边是数据库导航树,右边是工作区。我印象最深的是窗口标题栏那串 数据库开发工具 KStudio v2.0.0 - <kingbase>Script-77,Script 后面的数字一路往上涨,干一天活儿能涨到三位数。
KSQL Developer 是后来才上线的,2025 年 9 月份在金仓官网上放出第一个版本,版本号 V1R1C1,我写这篇的时候已经是 V1R1C2 了。它的定位不太一样:一款面向应用开发者的数据库开发和管理工具,除了自家的 KingbaseES,还能连 Oracle、MySQL、SQL Server。安装包给得很细,x86 的 Windows 和 Linux 各一份(大概 373M 上下),另外还有飞腾 Aarch64 和鲲鹏 Aarch64 的 Linux 包,信创环境能够直接落地。
最实用的差别在这儿:如果各位手上同时有金仓库、Oracle 库和 MySQL 库,以前得开三四个客户端来回切,现在一个 KSQL Developer 全装下了。连的时候选类型、填主机端口、测试连接、起个连接名,四步搞定。金仓库默认端口 54321,Oracle 是 1521,MySQL 3306,SQL Server 1433——这几个数字我闭着眼都能敲出来。
不过要说写 SQL 的手感,两个工具的 SQL 编辑器其实是同一套思路:语法高亮是基础,真正的区别在后面几层——基于数据库元数据的自动补全、语法检查、一键格式化、执行计划可视化、结果集直接编辑。这几层才是拉开效率差距的地方。
二、装完别急着写 SQL,先改三个设置
我有个习惯,新工具装上第一件事不是建连接,是先把默认设置撸一遍。磨刀不误砍柴工,这几个地方改完能省很多事。
第一,自动补全的触发字符。 默认一般只认 .,也就是各位打了表名加个点才弹提示。我会在首选项里把空格和回车也加上,再把补全的延迟时间调小一点。别小看这个,写长 SQL 的时候少敲一半字段名,一天下来手指头都轻松些。
第二,结果集默认取的行数。 大表查询默认只拉前 500 行,这个默认值是有道理的——一千万行的表各位要是 select * 不带 limit,客户端内存先扛不住。我一般保持默认,真要翻数据就用分页往下翻,或者加过滤条件。
第三,事务提交方式。 这是最要命的一个。工具栏上有「提交」和「回滚」两个按钮,旁边还有个「自动提交」的开关。我的规矩是:日常查询开自动提交,凡是带 update、delete、merge 的语句,一律关掉自动提交,手动点提交。写完先执行,看一眼影响行数对不对,对了再提交,不对直接回滚。开头说的那次事故,就是栽在这上面。
还有个细节,KSQL Developer 支持一键切换隔离模式,配合事务控制用起来很顺手。
三、建表:从模式开始,别什么都往 public 里塞
很多人建表上来就 create table xxx,结果全堆在 public 模式里,过两个月自己都找不着。我建议先按业务建模式,把命名空间隔干净。
-- 建一个业务模式,属主给 system
CREATE SCHEMA shop AUTHORIZATION system;
-- 商品主表
CREATE TABLE shop.product (
product_id INTEGER GENERATED BY DEFAULT AS IDENTITY (START WITH 1001 INCREMENT BY 1),
product_code VARCHAR(32) NOT NULL,
product_name VARCHAR(100) NOT NULL,
category VARCHAR(30) NOT NULL,
price NUMERIC(10,2) NOT NULL CHECK (price > 0),
stock_qty INTEGER NOT NULL DEFAULT 0 CHECK (stock_qty >= 0),
on_sale BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT pk_product PRIMARY KEY (product_id),
CONSTRAINT uk_product_code UNIQUE (product_code)
);
COMMENT ON TABLE shop.product IS '商品主表';
COMMENT ON COLUMN shop.product.price IS '售价,单位元,保留两位小数';
COMMENT ON COLUMN shop.product.stock_qty IS '可用库存,下单时扣减';
这里用 GENERATED BY DEFAULT AS IDENTITY 做自增主键,比自己建序列省事,SQL 标准写法,迁移的时候也少一层转换。要注意 GENERATED ALWAYS 和 GENERATED BY DEFAULT 的区别:前者不让你显式插主键值(除非写 OVERRIDING SYSTEM VALUE),后者允许。业务偶尔得手工指定 ID 的场景,用 BY DEFAULT 更灵活。
两个新手必踩的坑,我单独标出来:
int类型不能定长。写成int(10)、int(3)是会报错的,这是从 MySQL 转过来的人最容易犯的错,直接写int就行。varchar(n)里的 n 指的是字符数而不是字节数。因此varchar(100)存 100 个汉字没坑,这点对中文业务很友好。
drop partition 或者 alter table ... truncate partition,比 delete 快得多。
-- 客户表
CREATE TABLE shop.customer (
cust_id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
cust_name VARCHAR(50) NOT NULL,
phone VARCHAR(20),
city VARCHAR(30),
level_code VARCHAR(10) NOT NULL DEFAULT 'NORMAL',
reg_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT uk_customer_phone UNIQUE (phone)
);
-- 订单表:按下单时间做范围分区
CREATE TABLE shop.orders (
order_id BIGINT NOT NULL,
order_no VARCHAR(32) NOT NULL,
cust_id INTEGER NOT NULL,
total_amt NUMERIC(12,2) NOT NULL DEFAULT 0,
status VARCHAR(16) NOT NULL DEFAULT 'CREATED',
order_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT pk_orders PRIMARY KEY (order_id, order_time),
CONSTRAINT uk_order_no UNIQUE (order_no),
CONSTRAINT fk_orders_cust FOREIGN KEY (cust_id) REFERENCES shop.customer(cust_id),
CONSTRAINT ck_orders_status CHECK (status IN ('CREATED','PAID','SHIPPED','DONE','CANCELED'))
)
PARTITION BY RANGE (order_time) (
PARTITION p202601 VALUES LESS THAN ('2026-02-01'),
PARTITION p202602 VALUES LESS THAN ('2026-03-01'),
PARTITION p202603 VALUES LESS THAN ('2026-04-01'),
PARTITION pmax VALUES LESS THAN (MAXVALUE)
);
注意分区表的主键必须包含分区键,因此这里写的是 PRIMARY KEY (order_id, order_time)。这个规则卡得很死,第一次建分区表十有八九会在这儿报错。
订单明细表和索引:
CREATE TABLE shop.order_item (
item_id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
order_id BIGINT NOT NULL,
product_id INTEGER NOT NULL,
quantity INTEGER NOT NULL CHECK (quantity > 0),
unit_price NUMERIC(10,2) NOT NULL,
CONSTRAINT fk_item_product FOREIGN KEY (product_id) REFERENCES shop.product(product_id)
);
-- 高频查询字段建索引
CREATE INDEX idx_orders_cust_time ON shop.orders (cust_id, order_time);
CREATE INDEX idx_item_order ON shop.order_item (order_id);
我一般不给区分度低的字段单独建索引,举个例子 status 这种只有五个取值的列,建了也白建,还拖慢写入。
表建完不是就定了。 需求一变,ALTER TABLE 就得跟上,这几个动作我一并放出来:
-- 加一列,带默认值和非空
ALTER TABLE shop.customer ADD COLUMN last_login TIMESTAMP;
ALTER TABLE shop.customer ADD COLUMN remark VARCHAR(200) DEFAULT '' NOT NULL;
-- 改列的数据类型(varchar 加长是最常见的,缩长度要当心)
ALTER TABLE shop.product ALTER COLUMN product_name TYPE VARCHAR(200);
-- 加约束
ALTER TABLE shop.customer ADD CONSTRAINT ck_customer_level
CHECK (level_code IN ('NORMAL','SILVER','GOLD','PLATINUM'));
-- 删列、改列名
ALTER TABLE shop.customer DROP COLUMN remark;
ALTER TABLE shop.customer RENAME COLUMN city TO city_name;
改数据类型这一条要格外小心,现有数据得能转得过去,转不过去就报错,而且是大表的话耗时不短。我的做法是先查一遍有没有超长或者不兼容的值:
-- 改类型前先验一遍,心里有底
SELECT COUNT(*) FROM shop.product WHERE LENGTH(product_name) > 200;
再提醒一次,ALTER 这类 DDL 会触发隐式提交,别在一个没提交的事务中间穿插执行。
顺带说一句,在 KStudio 和 KSQL Developer 里,这些表都能够右键图形化创建,填表名、加列、选类型、勾约束,右边实时显示生成的 DDL。但我的建议是:图形化建表用来学习,正式环境还是写脚本。脚本能进版本库,能评审,能重放,图形化点出来的表你过三个月自己都记不清当初为什么这么设。
四、增:INSERT 的四种写法,第四种最好用
最基础的两种:
-- 单行插入,列名写全,别偷懒省略
INSERT INTO shop.customer (cust_name, phone, city, level_code)
VALUES ('张伟', '13800001111', '广州', 'GOLD');
-- 多行插入,一条语句搞定,比执行三次快
INSERT INTO shop.product (product_code, product_name, category, price, stock_qty)
VALUES ('KB-87-001', '机械键盘 87 键', '外设', 399.00, 120),
('MS-02-110', '无线鼠标 静音版', '外设', 89.00, 500),
('MN-27-4K', '27 英寸 4K 显示器', '显示', 1899.00, 60);
第三种是从别的表批量倒数据,做数据初始化和迁移时天天用:
-- 把老库客户表的数据灌进来,顺手做个字段转换
INSERT INTO shop.customer (cust_name, phone, city, level_code)
SELECT TRIM(old_name),
old_phone,
COALESCE(old_city, '未知'),
CASE WHEN old_score >= 1000 THEN 'GOLD' ELSE 'NORMAL' END
FROM legacy.t_customer
WHERE old_phone IS NOT NULL;
第四种是我现在最推荐的:用 MERGE INTO 做 upsert。有则更新,无则插入,一条语句解决,还避免了「先查再插」的并发竞态。
-- 商品同步:编码已存在就改价格和名称,不存在就新增
MERGE INTO shop.product t
USING (
SELECT 'KB-87-001' AS product_code,
'机械键盘 87 键 升级版' AS product_name,
429.00 AS price
) s
ON (t.product_code = s.product_code)
WHEN MATCHED THEN
UPDATE SET t.product_name = s.product_name,
t.price = s.price
WHEN NOT MATCHED THEN
INSERT (product_code, product_name, category, price, stock_qty)
VALUES (s.product_code, s.product_name, '外设', s.price, 100);
写 MERGE 有个细节,INSERT 分支的列名别加表别名前缀,VALUES 里引用源表的列才加 s.。这个我第一次写就报错了。
如果是 Oracle 兼容模式的库,MERGE INTO 能够直接用;要是库里没开兼容模式,写之前先确认一下,别想当然。
五、查:会写 SELECT 和会看执行计划,是两回事
一个带聚合的联表查询:
SELECT c.cust_name,
c.city,
COUNT(DISTINCT o.order_id) AS order_cnt,
SUM(o.total_amt) AS total_amount
FROM shop.customer c
JOIN shop.orders o ON o.cust_id = c.cust_id
WHERE o.status IN ('PAID','SHIPPED','DONE')
AND o.order_time >= DATE '2026-01-01'
AND o.order_time < DATE '2026-04-01'
GROUP BY c.cust_name, c.city
HAVING SUM(o.total_amt) > 5000
ORDER BY total_amount DESC
LIMIT 20 OFFSET 0;
分页用 LIMIT ... OFFSET ...,这是标准写法,从 0 开始计数。从 Oracle 迁移过来的同学要注意,Oracle 的 ROWNUM 从 1 开始,OFFSET 从 0 开始,页码换算的公式得改,不然第一页永远少一条。虽然金仓的兼容模式也认 ROWNUM,但我建议借迁移的机会统一改成 LIMIT/OFFSET,可读性和性能都更好。
执行计划是我认为 SQL 编辑器里最值钱的功能。 KStudio 里选中语句按 Ctrl+Shift+E(也有版本是 Ctrl+E),KSQL Developer 里右键或者工具栏都能调出来,会同时给出两种视图:一种是文本形式,能看到每一步的成本估算、行数估算、有没有走索引;另一种是树形或表格形式,把扫描、排序、聚合这些算子按层级铺开。调优的时候两个对着看,全表扫描、排序溢出、索引没命中,一眼就能定位。
其他几个高频快捷键,我贴在显示器边上:
Ctrl+Enter:执行当前 SQLCtrl+Shift+F:格式化 SQL,团队协作时保持风格统一Ctrl+空格:SQL 内容助理(手动唤出补全)Ctrl+Alt+空格:SQL 模板,可以把常用的片段存成模板,sel按 Tab 展开成select * from行计数:直接看表的行数估算,不用自己count(*)
六、改和删:慢一点,看一眼,再提交
改数据我最常用的是带子查询的更新,比关联更新写法更稳妥:
-- 单个客户升级等级
UPDATE shop.customer
SET level_code = 'GOLD'
WHERE cust_id = 1001;
-- 批量重算订单金额(从明细表汇总回来)
UPDATE shop.orders o
SET total_amt = (
SELECT COALESCE(SUM(i.quantity * i.unit_price), 0)
FROM shop.order_item i
WHERE i.order_id = o.order_id
)
WHERE o.status = 'CREATED';
扣库存这类既有更新又有条件的场景,我更爱用 MERGE,一次搞定:
-- 下单扣库存:库存够的才扣
MERGE INTO shop.product p
USING (
SELECT 1001 AS product_id, 2 AS buy_qty
) s
ON (p.product_id = s.product_id AND p.stock_qty >= s.buy_qty)
WHEN MATCHED THEN
UPDATE SET p.stock_qty = p.stock_qty - s.buy_qty;
把库存条件写进 ON 里,库存不足的自然就不会命中,MERGE 0 行,业务侧拿到返回行数就知道扣减失败了,比先查后改干净得多。
删除数据:
-- 按条件删,删之前先把同一套 where 条件放到 select 里跑一遍
SELECT COUNT(*) FROM shop.orders WHERE status = 'CANCELED' AND order_time < DATE '2025-01-01';
DELETE FROM shop.orders
WHERE status = 'CANCELED'
AND order_time < DATE '2025-01-01';
这个「先 select 后 delete」的动作我保持了快十年,雷打不动。条件一模一样,先跑 select count(*) 看数量对不对,对了再把 select count(*) 换成 delete。成本几秒钟,能挡掉九成的误删。
整表清空用 TRUNCATE,不走事务日志,快,但不可回滚,只在初始化和清测试数据时用:
TRUNCATE TABLE shop.order_item;
还有一条容易忽略的:DDL 语句前后会触发隐式提交。也就是说,你开了一个事务,insert 完还没提交,紧接着执行一条 alter table,前面那条 insert 就被自动提交了。这个行为在官方文档里写得明明白白,但真到线上出事的时候,很多人反应不过来。
七、结果集直接改数据,方便但别上瘾
KSQL Developer 的数据编辑器做得挺像 Excel:双击单元格直接改,改完点保存,工具在后台生成对应的 UPDATE 语句发出去。KStudio 里也有类似的即时编辑。查出来的结果集还能直接排序、过滤,右键导出成 CSV、Excel、XLSX、JSON、Markdown、TXT、XML,甚至能导出成一堆 INSERT 语句——这个在做跨环境数据搬运时格外香。
但我得泼一盆冷水:这个功能适合小批量修数据,不适合当日常操作。原因有两个。一是它生成的语句你看不见(除非去执行日志里翻),改错了不知道自己改了什么;二是频繁的单行更新在数据库侧就是一堆小事务,性能远不如一条批量的 update。
导出大表也得注意。结果集导出是把数据先拉到客户端内存里再写文件,几百万行直接 OOM。大表老老实实用 sys_dump 在服务端流式导出,稳。
八、几个不起眼但能救命的编辑器功能
这一节说的都是我用了大半年才发现、然后后悔没早点用的东西。
语法检查。 编辑器左侧那条小横线或者右键菜单里的「语法检查」,能在你不执行语句的前提下把拼写错误标出来。别觉得这是废话——一条跑二十分钟的批量脚本,执行到第三分钟才报「第 47 行语法错误」,那种挫败感谁经历谁知道。写长脚本前先过一遍语法检查,省的是自己的时间。
执行历史。 KSQL Developer 会保留执行过的语句,同一个标签页里也能存多个结果集。我改 SQL 的习惯是在一个页里反复迭代,改一版跑一次,前面的结果集留着对比。以前没有这个功能的时候,我靠不停 Ctrl+C 把旧 SQL 存到记事本里,笨得很。
SQL 模板。 内置的模板已经够用,Ctrl+Alt+空格 唤出,还能自己扩展。我把项目里那几条固定查询——举个例子「按客户维度汇总月度销售额」——都存成了模板,输入缩写按 Tab 就展开,字段名和格式都是统一的,交给同事也不会跑偏。
多结果集与标签页。 一次执行多条 SQL,结果分标签页展示,切换比命令行方便太多。我调试存储过程的时候经常一次跑五六条验证语句,全在底下排着,扫一眼就知道哪一步的数据不对。
会话监控和锁等待。 这个偏 DBA 向,但开发者也该知道在哪儿看。KSQL Developer 里能实时看当前活跃会话、正在执行的 SQL、锁等待情况。有一次线上一个事务卡住了,后面一堆请求排队,我就是在这儿看到一条 update 挂了十几分钟没提交,定位到是哪个服务实例的连接没关。功能藏得有点深,但用上一次就值回票价。
导入导出向导。 支持 Excel、CSV、XML、TXT 这些格式的双向传输,向导式操作,还能做字段映射和类型转换。这里有个坑我踩过:导 CSV 的时候中文乱码,十有八九是编码没对上,源文件是 GBK 的必须在导入设置里显式指定,别指望工具自己猜。
九、最后说几句心里话
工具这东西,功能再多,用不上等于没有。SQL 编辑器真正的价值不在于它能把关键字染成蓝色,而在于它把「写代码—看执行计划—改数据—验证」这个闭环缩短到了几秒钟。我现在写一条麻烦 SQL,习惯是:写一半用补全补齐字段名,写完 Ctrl+Shift+F 格式化,先 Ctrl+Shift+E 看一眼执行计划,再 Ctrl+Enter 跑结果,确认无误才提交。整套动作下来不到一分钟,但每一步都在替我兜底。
KStudio 胜在跟 KingbaseES 的老版本贴得紧、功能全;KSQL Developer 胜在跨库统一、界面现代、信创平台覆盖全。两个我都装着,按场景换着用。如果你刚接触金仓这套生态,我建议从 KSQL Developer 入手,门槛低一点,上手快一点。
代码这东西,看十遍不如敲一遍。上面的脚本你随便找个测试库跑一遍,比读十篇文章管用。
以上就是这次整理的全部内容,希望对你有所启发。如果有不同见解,欢迎在评论区交流讨论。
评论 (0)
暂无评论