原始网页:https://www.cockroachlabs.com/docs/stable/multi-active-availability.html


CockroachDB在线更新Schema的功能,提供了一个简便的方式来更新表的Schema,不需要进行对应用程序有任何负面影响的操作(例如服务下线)。相关的Schema变更引擎属于CockroachDB内置的功能,不需要额外配置工具和资源,也不需要进行特别的操作。

在线变更Schema带来的好处包括:

TIPS:事务中变更Schema的功能是受限的。我们推荐在事务外进行Schema变更操作。如果一个Schema变更工具自动为用户创建事务,我们推荐每个事务只进行一个Schema变更操作。

Schema在线变更功能的工作原理

在数据库上层,在线变更Schema功能的实现是使用多版本Schema,通过桥接策略实现的。流程如下:

  1. 用户通过执行ALTER TABLECREATE INDEXTRUNCATE等语句发起Schema变更操作。
  2. Schema变更引擎在后台发起一个任务,将旧的Schema变更为新的Schema。变更操作在完成之前隔离于数据库当前状态,以保证基础表数据的一致性状态。

这种方式允许Schema变更引擎在数据库使用上一个版本Schema的过程中变更Schema,然后在集群运行(正常处理读、写请求)过程中,根据需要回填或是删除旧的基础表数据。

在整个回填的过程中,Schema变更引擎会更新基础表数据,同时确保所有表存储的内容能够满足新Schema的要求。

一旦回填完成,所有节点会切换至新的Schema,使用新的Schema进行读写请求操作。

更多技术细节,可以查看CockroachDB如何实现在线Schema变更

示例

TIPS: Schema变更语句的更多示例,可以查看ALTER TABLE的子命令。

在事务中使用CREATE TABLE语句变更Schema

Limitations里提到,一般来说不允许用户在事务中进行Schema变更操作。

然而从2.1版本开始在特定场景下这是可能的,例如用户能够在使用CREATE TABLE的同一个事务中变更Schema,例如:

BEGIN;
SAVEPOINT cockroach_restart;
CREATE TABLE fruits (
      id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
      name STRING,
      color STRING
  );
INSERT INTO fruits (name, color) VALUES ('apple', 'red');
ALTER TABLE fruits ADD COLUMN inventory_count INTEGER DEFAULT 5;
ALTER TABLE fruits ADD CONSTRAINT name CHECK (name IN ('apple', 'banana', 'orange'));
SELECT name, color, inventory_count FROM fruits;
RELEASE SAVEPOINT cockroach_restart;
COMMIT;

事务输出:

BEGIN
SAVEPOINT
CREATE TABLE
INSERT 0 1
ALTER TABLE
ALTER TABLE
+-------+-------+-----------------+
| name  | color | inventory_count |
+-------+-------+-----------------+
| apple | red   |               5 |
+-------+-------+-----------------+
(1 row)
COMMIT
COMMIT

显示所有schema变更任务

用户可以使用SHOW JOBS语句随时检查数据库系统中Schema变更任务的状态:

SELECT * FROM [SHOW JOBS] WHERE job_type = 'SCHEMA CHANGE';

+--------------------+---------------+-----------------------------------------------------------------------------+-----------+-----------+----------------------------+----------------------------+----------------------------+----------------------------+--------------------+-------+----------------+
|             job_id | job_type      | description                                                                 | user_name | status    | created                    | started                    | finished                   | modified                   | fraction_completed | error | coordinator_id |
|--------------------+---------------+-----------------------------------------------------------------------------+-----------+-----------+----------------------------+----------------------------+----------------------------+----------------------------+--------------------+-------+----------------|
| 368863345707909121 | SCHEMA CHANGE | ALTER TABLE test.public.fruits ADD COLUMN inventory_count INTEGER DEFAULT 5 | root      | succeeded | 2018-07-26 20:55:59.698793 | 2018-07-26 20:55:59.739032 | 2018-07-26 20:55:59.816007 | 2018-07-26 20:55:59.816008 |                  1 |       | NULL           |
| 370556465994989569 | SCHEMA CHANGE | ALTER TABLE test.public.foo ADD COLUMN bar VARCHAR                          | root      | pending   | 2018-08-01 20:27:38.708813 | NULL                       | NULL                       | 2018-08-01 20:27:38.708813 |                  0 |       | NULL           |
| 370556522386751489 | SCHEMA CHANGE | ALTER TABLE test.public.foo ADD COLUMN bar VARCHAR                          | root      | pending   | 2018-08-01 20:27:55.830832 | NULL                       | NULL                       | 2018-08-01 20:27:55.830832 |                  0 |       | NULL           |
+--------------------+---------------+-----------------------------------------------------------------------------+-----------+-----------+----------------------------+----------------------------+----------------------------+----------------------------+--------------------+-------+----------------+
(1 row)

Limitations

概述

Schema变更操作能够保障在任何时间点下数据的一致性,在通常情况下不应该在事务中进行。这是为了让集群在变更操作执行的同时,能够保持在线的状态,继续处理来自应用程序的读、写请求。

具体地说,使用事务变更Schema意味着需要将给定的Schema变更信息传播到集群当中的所有节点,在传播的过程当中会阻塞所有由用户发起的事务。因为Schema变更的事务需要在其他事务开始之前提交。而在提交完成之前,集群不会处理读、写请求,应用程序的服务将会下线。

TIPS: 从2.1版本开始,用户能够在使用CREATE TABLE的同一个事务中变更Schema。

在事务中不允许Schema变更

在一个事务当中:

在prepared语句执行过程中不允许Schema变更

如果prepared语句在执行之前,其引用的表的Schema发生变更,那么CockroachDB将使用变更后的Schema返回prepared语句执行的结果。

例如:

CREATE TABLE users (id INT PRIMARY KEY);
PREPARE prep1 AS SELECT * FROM users;
ALTER TABLE users ADD COLUMN name STRING;
INSERT INTO users VALUES (1, 'Max Roach');
EXECUTE prep1;

+----+-----------+
| id |   name    |
+----+-----------+
|  1 | Max Roach |
+----+-----------+
(1 row)

因此我们不推荐通过prepared语句或是其他方式去反复执行SELECT *语句。此外,如果在prepared语句执行之前Schema发生变更,执行prepared的INSERTUPSERTDELETE语句会产生一些不一致的行为:

语句执行错误的示例

以下语句由于违反““事务中不允许Schema变更”的限制,将执行错误:

在同一个事务当中创建索引,并使用索引执行SELECT语句

CREATE TABLE foo (id INT PRIMARY KEY, name VARCHAR);
BEGIN;
SAVEPOINT cockroach_restart;
CREATE INDEX foo_idx ON foo (id, name);
SELECT * from foo_idx;
RELEASE SAVEPOINT cockroach_restart;
COMMIT;

执行结果:

REATE TABLE
BEGIN
SAVEPOINT
CREATE INDEX
ERROR:  relation "foo_idx" does not exist
ERROR:  current transaction is aborted, commands ignored until end of transaction block
ROLLBACK

在同一个事务当中增加列,并为该列添加约束

CREATE TABLE foo ();
BEGIN;
SAVEPOINT cockroach_restart;
ALTER TABLE foo ADD COLUMN bar VARCHAR;
ALTER TABLE foo ADD CONSTRAINT bar CHECK (foo IN ('a', 'b', 'c', 'd'));
RELEASE SAVEPOINT cockroach_restart;
COMMIT;

执行结果:

CREATE TABLE
BEGIN
SAVEPOINT
ALTER TABLE
ERROR:  column "foo" not found for constraint "foo"
ERROR:  current transaction is aborted, commands ignored until end of transaction block
ROLLBACK

在同一个事务当中添加列,并使用SELECT语句(引用新列)

CREATE TABLE foo ();
BEGIN;
SAVEPOINT cockroach_restart;
ALTER TABLE foo ADD COLUMN bar VARCHAR;
SELECT bar FROM foo;
RELEASE SAVEPOINT cockroach_restart;
COMMIT;

执行结果:

CREATE TABLE
BEGIN
SAVEPOINT
ALTER TABLE
ERROR:  column name "bar" not found
ERROR:  current transaction is aborted, commands ignored until end of transaction block
ROLLBACK