运营要一份订单数据、产品要个对账表,这活儿最后常常落到开发头上。MySQL 里把查询结果导成文件,第一反应是 SELECT ... INTO OUTFILE:可它要 FILE 权限,文件还落在数据库服务器上,开发机上看不到,经常得再登服务器把文件拷出来。
KingbaseES 里有个更顺手的做法------ksql 的 \copy。它是客户端元命令,由 ksql 进程在本机执行:文件直接写在运行 ksql 的这台机器上,普通账号就能用,不碰服务端权限。
这篇在 KingbaseES V009R001C010 环境里验证 \copy 的三件事:把整张表导成 CSV、按条件只导需要的列和行、再把 CSV 导回表;最后用一条不带反斜杠的 COPY 触发报错,说清 \copy 和 COPY 到底差在哪。演示用普通用户 app_user 连接 app_db 库,表是 app_schema.t_meta_demo。
先确认连的是哪里
\copy 跑在当前会话里,用短表名时靠 search_path 找表,所以连进去先把位置和身份确认清楚:
csharp
set search_path to app_schema, public;
select current_database(), current_user, current_schema();

当前库是 app_db,用户是 app_user,search_path 第一个 schema 是 app_schema。注意这里是普通用户 ------这一点到第 6 节会很关键,它正是 \copy 能用、而服务端 COPY 用不了的分界线。
导出整张表
把 t_meta_demo 整张表导成带列名的 CSV:
sql
\copy t_meta_demo TO '/tmp/orders_full.csv' WITH (FORMAT CSV, HEADER true);
ksql 返回 COPY 5,表示写出了 5 行。在另一个终端 cat 这个文件:
bash
id,order_no,user_name,status,amount,created_at
1,ORD-20240601-001,alice,paid,1280.50,2026-06-12 14:39:09.209084
2,ORD-20240601-002,bob,pending,360.00,2026-06-12 14:39:09.209084
3,ORD-20240601-003,carol,shipped,899.00,2026-06-12 14:39:09.209084
4,ORD-20240601-004,alice,paid,75.20,2026-06-12 14:39:09.209084
5,ORD-20240601-005,dave,refund,2100.00,2026-06-12 14:39:09.209084

几个点:
- 文件路径
/tmp/orders_full.csv是本机路径(运行 ksql 的客户端这台机器),不是数据库服务器上的路径。 HEADER true让第一行输出列名,运营拿到手直接能用。- 整表导出会带上所有字段,包括
id和created_at。
MySQL 里做同样的事是 SELECT * FROM ... INTO OUTFILE '/path/orders.csv':文件生成在数据库服务器上,需要 FILE 权限,开发机上还看不到。\copy 没有这些限制------文件落在哪、用什么账号,由运行 ksql 的人决定。另外 \copy 是 ksql 元命令,必须写在一行,不能像普通 SQL 那样折行。
只导需要的列和行
实际场景很少导整表。运营要的往往是「状态 paid 的订单,只要订单号、用户名、金额」。\copy 后面直接跟一段 SELECT 就行:
sql
\copy (select order_no, user_name, amount from t_meta_demo where status = 'paid') TO '/tmp/orders_paid.csv' WITH (FORMAT CSV, HEADER true);
返回 COPY 2,文件内容:
order_no,user_name,amount
ORD-20240601-001,alice,1280.50
ORD-20240601-004,alice,75.20

括号里是任意 SELECT,WHERE、字段筛选、ORDER BY、JOIN 都能写。这是最实用的形式:不用建临时表,需要什么直接查出来写进文件。CSV 的列名取自 SELECT 的字段------加了别名就用别名,做模板化导出时这个细节很有用。
准备一份要导入的 CSV
反过来,运营发来一批要补录的新订单,CSV 格式。先用 !(在 ksql 里执行 shell 命令)造一个文件出来:
bash
! printf 'order_no,user_name,status,amount\nORD-20240602-001,eve,paid,560.00\nORD-20240602-002,frank,pending,120.50\nORD-20240602-003,grace,shipped,3200.00\n' > /tmp/orders_import.csv
cat 确认内容:
lua
order_no,user_name,status,amount
ORD-20240602-001,eve,paid,560.00
ORD-20240602-002,frank,pending,120.50
ORD-20240602-003,grace,shipped,3200.00

这份 CSV 只给了 order_no、user_name、status、amount 四列,没有 id 和 created_at------它们一个走自增序列、一个走 now() 默认值,不需要在文件里提供。
把 CSV 导进表
导入时用括号指定 CSV 的列对应表里的哪几列:
sql
\copy t_meta_demo (order_no, user_name, status, amount) FROM '/tmp/orders_import.csv' WITH (FORMAT CSV, HEADER true);
返回 COPY 3。查一下结果(这里开了展开模式,字段竖排更好认):
vbnet
select * from t_meta_demo order by id;

原来的 5 条加上新导入的 3 条,一共 8 条。新数据的 id 由序列自动生成、created_at 自动填入导入时刻的时间------CSV 里没提供这两列,它们各自走了默认值。
两个关键点:
- 列名括号
(order_no, user_name, status, amount):告诉 ksql CSV 里的数据按顺序填进这几列,没列出来的列走默认值。不写这个括号时,ksql 会按表的全部列顺序去对 CSV,列数对不上就报错。 HEADER true在导入时的作用和导出相反:导出时它让第一行输出列名;导入时它告诉 ksql「第一行是列名,跳过、别当数据」。同一个选项,两个方向各管一头。
MySQL 里对应的是 LOAD DATA INFILE,同样在服务端执行、文件要在服务器上,除非加 LOCAL 关键字走客户端。\copy 不挑这些,文件在 ksql 所在的机器上就能读。
\copy 和 COPY 只差一个反斜杠,执行的人完全不同
把前面成功的命令去掉反斜杠,用同一个 app_user 再跑一次:
sql
COPY t_meta_demo TO '/tmp/test_server_copy.csv' WITH (FORMAT CSV, HEADER true);
这次直接报错:
vbnet
ERROR: must be superuser or a member of the sys_write_server_files role to COPY to a file
HINT: Anyone can COPY to stdout or from stdin. ksql's \copy command also works for anyone.

同一张表、同样的路径和选项,加不加反斜杠,结果天差地别:
COPY(不带反斜杠)是 SQL 命令,由数据库服务端执行。 它要往文件写,路径是服务器上的路径 ,需要超级用户或sys_write_server_files角色。app_user是普通用户,直接被拦下。\copy(带反斜杠)是 ksql 元命令,由 ksql 进程在本机执行。 路径是客户端本机路径,用的是当前操作系统账号的文件权限,跟数据库里有没有特权无关------这正是前面几节普通用户都能跑通的原因。
报错的 HINT 把这层关系说得很直白:Anyone can COPY to stdout or from stdin. ksql's \copy command also works for anyone.------谁都能用 \copy。
小结
记一条就够:看反斜杠。
\copy:客户端命令,文件在你运行 ksql 的机器 上,用你的系统账号权限,普通数据库用户就能用。日常给运营导数据、补录数据,用它。COPY:服务端命令,文件在数据库服务器 上,要超级用户或sys_write_server_files角色。它是 DBA 在服务端做批量迁移时用的。
开发日常最顺手的一条,是 \copy (SELECT ...) TO '文件' WITH (FORMAT CSV, HEADER true):条件、字段自己挑,HEADER true 带上列名,一条命令把运营要的数据落到本地。
最后一个编码上的坑:如果 CSV 是 Windows 下 Excel 导出的,多半是 GBK 编码,直接导入导出中文会乱码。在 WITH 里加 ENCODING 'GBK' 指定文件编码即可,不用先转码。
这也是 \copy 和 MySQL SELECT INTO OUTFILE / LOAD DATA INFILE 最本质的区别:不是语法不同,而是执行的那一端不同------一个在客户端,一个在服务端。想清楚文件该落在哪、用谁的权限,选哪个就清楚了。