Skip to content

Commit ed02099

Browse files
committed
support online parameter when create index
1 parent fe799d4 commit ed02099

4 files changed

Lines changed: 516 additions & 0 deletions

File tree

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
:sectnums:
2+
:sectnumlevels: 5
3+
4+
:imagesdir: ./_images
5+
6+
= 索引 ONLINE 参数
7+
8+
== 目的
9+
10+
IvorySQL 数据库支持在创建索引时使用 `ONLINE` 参数,用于在线创建索引,同时不阻塞DML操作。
11+
12+
== 实现说明
13+
14+
=== 数据结构扩展
15+
16+
`IndexStmt` 新增 `online_keyword` 字段。
17+
18+
[source,c]
19+
----
20+
bool transformed; /* true when transformIndexStmt is finished */
21+
bool concurrent; /* should this be a concurrent index build? */
22+
bool online_keyword; /* was ONLINE keyword used (as opposed to CONCURRENTLY)? */
23+
----
24+
25+
=== 语法与解析
26+
27+
==== 语法规则扩展
28+
29+
在 `ora_gram.y` 文件中引入一个弹性选项列表 `create_index_opt_list` / `create_index_opt`,类似现有的 `rebuild_index_opt_list`:
30+
31+
[source,yacc]
32+
----
33+
create_index_opt_list:
34+
create_index_opt_list create_index_opt { $$ = lappend($1, $2); }
35+
| /* EMPTY */ { $$ = NIL; }
36+
;
37+
38+
create_index_opt:
39+
ONLINE
40+
{ $$ = makeDefElem("online", (Node *) makeBoolean(true), @1); }
41+
| TABLESPACE name
42+
{ $$ = makeDefElem("tablespace", (Node *) makeString($2), @1); }
43+
;
44+
----
45+
46+
修改 `IndexStmt` 的两个产生式,将 `OptTableSpace` 替换为 `create_index_opt_list`,并在 action 中从选项列表中提取 `online` 和 `tablespace`。
47+
[source,yacc]
48+
----
49+
IndexStmt: CREATE opt_unique INDEX opt_concurrently opt_single_name
50+
ON relation_expr access_method_clause '(' index_params ')'
51+
opt_include opt_unique_null_treatment opt_reloptions
52+
create_index_opt_list where_clause
53+
{
54+
IndexStmt *n = makeNode(IndexStmt);
55+
bool online = false;
56+
bool online_seen = false;
57+
char *tablespace = NULL;
58+
ListCell *lc;
59+
60+
/* 解析 create_index_opt_list,提取 online 和 tablespace */
61+
foreach(lc, $15)
62+
{
63+
DefElem *opt = (DefElem *) lfirst(lc);
64+
65+
if (strcmp(opt->defname, "online") == 0)
66+
{
67+
if (online_seen)
68+
ereport(ERROR,
69+
(errcode(ERRCODE_SYNTAX_ERROR),
70+
errmsg("ONLINE specified multiple times"),
71+
parser_errposition(opt->location)));
72+
online = defGetBoolean(opt);
73+
online_seen = true;
74+
}
75+
else if (strcmp(opt->defname, "tablespace") == 0)
76+
{
77+
if (tablespace != NULL)
78+
ereport(ERROR,
79+
(errcode(ERRCODE_SYNTAX_ERROR),
80+
errmsg("TABLESPACE specified multiple times"),
81+
parser_errposition(opt->location)));
82+
tablespace = defGetString(opt);
83+
}
84+
}
85+
86+
/* CONCURRENTLY 与 ONLINE 互斥 */
87+
if ($4 && online)
88+
ereport(ERROR,
89+
(errcode(ERRCODE_SYNTAX_ERROR),
90+
errmsg("cannot use both CONCURRENTLY and ONLINE"),
91+
parser_errposition(@4)));
92+
93+
n->unique = $2;
94+
n->concurrent = $4 || online;
95+
n->online_keyword = online;
96+
n->idxname = $5;
97+
n->relation = $7;
98+
n->accessMethod = $8;
99+
n->indexParams = $10;
100+
n->indexIncludingParams = $12;
101+
n->nulls_not_distinct = !$13;
102+
n->options = $14; /* opt_reloptions (WITH clause) */
103+
n->tableSpace = tablespace;
104+
n->whereClause = $16;
105+
n->excludeOpNames = NIL;
106+
n->idxcomment = NULL;
107+
n->indexOid = InvalidOid;
108+
n->oldNumber = InvalidRelFileNumber;
109+
n->oldCreateSubid = InvalidSubTransactionId;
110+
n->oldFirstRelfilelocatorSubid = InvalidSubTransactionId;
111+
n->primary = false;
112+
n->isconstraint = false;
113+
n->deferrable = false;
114+
n->initdeferred = false;
115+
n->transformed = false;
116+
n->if_not_exists = false;
117+
n->reset_default_tblspc = false;
118+
$$ = (Node *) n;
119+
}
120+
----
121+
122+
==== 执行层修改(`indexcmds.c`)
123+
124+
`DefineIndex()` 中已有临时表降级逻辑(`concurrent = false` when temp table),需在相同位置补充分区表的降级逻辑:
125+
126+
[source,c]
127+
----
128+
/* 已有:临时表降级 */
129+
if (stmt->concurrent && get_rel_persistence(tableId) != RELPERSISTENCE_TEMP)
130+
concurrent = true;
131+
else
132+
concurrent = false;
133+
134+
/* 新增:分区表降级
135+
* Oracle 对分区表 CREATE INDEX ONLINE 返回成功(全局非分区索引)。
136+
* PostgreSQL 的 concurrent + partitioned 路径会报错,因此在 Oracle 解析器
137+
* 模式下(stmt->online_keyword = true)对分区表静默降级为普通构建。
138+
*/
139+
if (concurrent && partitioned && stmt->online_keyword)
140+
concurrent = false;
141+
----
142+
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
:sectnums:
2+
:sectnumlevels: 5
3+
4+
:imagesdir: ./_images
5+
6+
= 索引 ONLINE 参数
7+
8+
== 目的
9+
10+
本文档解释 IvorySQL 中 `ONLINE` 参数在创建索引时的功能,实现该参数功能以保持与 Oracle 行为一致。
11+
12+
== 功能说明
13+
14+
- `ONLINE` 参数在创建索引时被指定,可允许 DML 并发执行,类似 PostgreSQL 中的 `CONCURRENTLY`,但不能与 `CONCURRENTLY` 同时出现。
15+
- `ONLINE` 必须支持出现在列列表 `)` 之后、`WHERE` 子句之前的任意位置,且与 `TABLESPACE`、`PARALLEL`(如已支持)等其他属性的顺序无关。
16+
- 临时表上的 `CREATE INDEX ... ONLINE` 自动降级为普通构建(不报错,与 `CONCURRENTLY` 行为一致)。
17+
- 分区表上的 `CREATE INDEX ... ONLINE` **自动降级为普通构建**(不报错)。
18+
19+
== 测试用例
20+
21+
=== 测试环境准备
22+
23+
[source,sql]
24+
----
25+
-- 基础测试表
26+
CREATE TABLE tbl_ci_online (
27+
id NUMBER(10) PRIMARY KEY,
28+
name VARCHAR2(100),
29+
dept_id NUMBER(10),
30+
salary NUMBER(10,2),
31+
status VARCHAR2(20)
32+
);
33+
INSERT INTO tbl_ci_online
34+
SELECT g, 'name'||g, MOD(g,20), g*100.0, CASE WHEN MOD(g,2)=0 THEN 'ACTIVE' ELSE 'INACTIVE' END
35+
FROM generate_series(1, 1000) g;
36+
37+
-- 唯一索引测试表
38+
CREATE TABLE tbl_ci_unique (
39+
id NUMBER(10) PRIMARY KEY,
40+
email VARCHAR2(200) NOT NULL
41+
);
42+
INSERT INTO tbl_ci_unique SELECT g, 'user'||g||'@example.com' FROM generate_series(1, 200) g;
43+
44+
-- 分区表
45+
CREATE TABLE tbl_ci_part (
46+
id NUMBER(10),
47+
region VARCHAR2(20)
48+
) PARTITION BY RANGE (id);
49+
CREATE TABLE tbl_ci_part_p1 PARTITION OF tbl_ci_part FOR VALUES FROM (1) TO (501);
50+
CREATE TABLE tbl_ci_part_p2 PARTITION OF tbl_ci_part FOR VALUES FROM (501) TO (1001);
51+
INSERT INTO tbl_ci_part SELECT g, CASE WHEN g<=500 THEN 'east' ELSE 'west' END
52+
FROM generate_series(1, 1000) g;
53+
----
54+
55+
=== 基础 ONLINE 构建
56+
57+
[source,sql]
58+
----
59+
-- 最简单形式
60+
CREATE INDEX idx_online_name ON tbl_ci_online (name) ONLINE;
61+
62+
-- 验证索引已建立并有效
63+
SELECT indisvalid FROM pg_index
64+
WHERE indexrelid = 'idx_online_name'::regclass;
65+
-- 期望:t
66+
67+
-- 多列索引
68+
CREATE INDEX idx_online_multi ON tbl_ci_online (dept_id, salary) ONLINE;
69+
70+
-- 表达式索引
71+
CREATE INDEX idx_online_expr ON tbl_ci_online (lower(name)) ONLINE;
72+
----
73+
74+
=== 分区表 ONLINE
75+
76+
[source,sql]
77+
----
78+
-- 分区表父级索引 ONLINE
79+
-- 注:ONLINE 在分区表上静默降级为普通构建,与 Oracle 行为一致
80+
CREATE INDEX idx_part_online ON tbl_ci_part (id) ONLINE;
81+
82+
-- 验证索引已创建且有效(降级为普通构建,父级索引 + 各分区子索引均 VALID)
83+
SELECT c.relname, i.indisvalid
84+
FROM pg_index i
85+
JOIN pg_class c ON c.oid = i.indexrelid
86+
WHERE c.relname LIKE '%idx_part_online%'
87+
ORDER BY c.relname;
88+
-- 期望:idx_part_online(父)及两个分区子索引 indisvalid = t
89+
90+
-- 分区表 ONLINE + TABLESPACE
91+
CREATE INDEX idx_part_online_tbs ON tbl_ci_part (region) ONLINE TABLESPACE pg_default;
92+
----
93+
94+
=== CONCURRENTLY 与 ONLINE 互斥
95+
96+
[source,sql]
97+
----
98+
-- CONCURRENTLY 在前,ONLINE 在后
99+
CREATE INDEX CONCURRENTLY idx_both ON tbl_ci_online (name) ONLINE;
100+
-- 期望:ERROR: cannot use both CONCURRENTLY and ONLINE
101+
102+
-- 验证原有 CONCURRENTLY 语法不受影响
103+
CREATE INDEX CONCURRENTLY idx_still_conc ON tbl_ci_online (dept_id);
104+
-- 期望:成功
105+
----
106+
107+
=== 测试环境清理
108+
109+
[source,sql]
110+
----
111+
DROP TABLE IF EXISTS tbl_ci_online CASCADE;
112+
DROP TABLE IF EXISTS tbl_ci_part CASCADE;
113+
DROP TABLE IF EXISTS tbl_ci_unique CASCADE;
114+
----
115+

0 commit comments

Comments
 (0)