李博洋
OceanBase 技术部研发工程师。
OceanBase 开源社区里经常会看到一些类似于 “ schema 是什么” 的疑问:
很多同学经常会误以为在 OceanBase 里,schema 只是 database 的同义词,这次分享就从 schema 是什么这个问题稍微展开聊一下。
首先说结论,schema 这个东西在 MySQL( OceanBase 的 MySQL 模式)、Oracle( OceanBase 的 Oracle 模式)、OceanBase 元数据管理模块中的含义不同。
OceanBase MySQL 模式中 schema 的概念
Schema 是 Database 的同义词。SQL 中可以使用 schema 关键字代替 Database 关键字,例如使用 CREATE SCHEMA 代替 CREATE DATABASE 等。
OceanBase Oracle 模式中 schema 的概念
在 OceanBase 的 Oracle 模式中,schema 是指一个用户所拥有的数据库对象的集合,用于权限管理和命名空间隔离,我个人把它理解成一个 “用户空间”。schema 对象是指在某个 schema 中的数据库对象,例如 schema 中的表、视图、索引等;非 schema 对象是指不属于某个 schema 的数据库对象,例如用户、角色、表空间等。
用户在创建时会拥有一个缺省的 schema,其 schema 名就等于用户名。如果有权限的话,用户还可以访问和使用其他的 schema。在访问一个 schema 中的对象时,如果没有指明该对象属于哪一个 schema,系统就会自动给对象加上缺省的 schema 名称。
如果当前 user 拥有访问或修改其他 schema 对象的权限,可以通过 alter session set current_schema = other_schema_name ; 切换到其他 schema 中进行各种操作。
OB 元数据管理模块中 schema 的概念
Oceanbase 元数据管理模块里的 schema 泛指一切需要在集群范围内同步的数据库对象元信息,包括但不限于 table、database、user 等元信息。此外,Oceanbase 的 schema 是多版本的,内存中的 schema 信息在集群范围的同步是最终一致的。
schema 里有什么?
schema 是什么解释完了,在社区里又会看到有人问,schema 是元信息,那么元信息里包含了哪些东西?
上面的回答中其实有个小的疏漏,因为各种数据库对象的元数据信息只会受 DDL 的影响,“预估行数” 属于不受 DDL 的影响,只受 DML 影响的统计信息,所以其实并不是表的元信息,table schema 中也不会对其进行记录。
元信息里具体包含了哪些东西,可以参见 src / share / schema 路径下的代码。例如如果想看 table schema 中记录了哪些 table 的元数据信息,看 ob_table_schema.h 中的 ObTableSchema 及其父类有哪些类成员即可。
DDL 执行过程
上面回答了 schema 是什么、有什么的问题。因为 schema 只会通过 DDL 进行修改,所以这里简单提一下 DDL 的执行过程,方便大家在遇到 DDL 相关问题时进行排查。
DDL 不会被优化器处理,而是作为 command 发送到 RootServer,由 RootServer(下简称 rs ) 进行处理。在 OceanBase 里的执行流程如下:
以一个最常见的建表语句为例:
create table 命令会在 obs 上对建表语句进行 resolve,把建表的信息存到 create_table_arg 中,把 create_table_arg 发 rpc 给 rs ,rs 接下来会来执行如下操作:
检查 obs 在 resolve 时使用的的 schema 版本是否最新(采用乐观锁的方法解决,如果非最新,则对这条 DDL 进行整体重试);
从 __all_sys_stat 里获取一个租户内单调递增的新 table id
把 create_table_arg 里提供的信息插入到 __all_table_history 等内部表里用于持久化
在 __all_ddl_operation 中记录 ddl 的变更日志(用于增量刷新等场景)
publish schema(通知各节点把 schema 刷到内存里)
其他 observer 接收到 RS 发送的 publish schema 的命令之后,就会把内部表中的改动增量加载到内存( schema cache )中,这也就是我们经常听到别人说的 “刷 schema ”。
rs 上的 ddl_service 调用 publish_schema () 将新的 schema 版本号广播给所有 obs 实际发生了什么?
rs 自己所在的 obs 直接调用 refresh_schema 。
给每个 alive obs 发送 switch_schema 的命令,参数为最新 schema_version 。
各个 obs 收到指令后,生成一个 ObSchemaRefreshTask 异步刷新任务,通过这行这个任务把自己的 schema 刷到最新。
附另一张图:
图中上面的部分是在执行 DDL,RS 的 DDL service 服务会负责写内部表和通知各 observer 节点把元数据的修改加载到内存的 schema cache 中;
下面的部分是在执行 query 的过程,过程中几乎都会读取内存中 schema cache 的元信息。
一开始客户在社区里提的那个问题中的 GV$OB_SERVER_SCHEMA_INFO 可以理解为每台 ObServer 每个租户已经刷新的最新版本的 schema 的信息,这个视图用户比较关注的 schema 信息是 REFRESHED_SCHEMA_VERSION 、SCHEMA_COUNT 、SCHEMA_SIZE ,其含义如下:
REFRESHED_SCHEMA_VERSION :对应租户在对应机器已刷新到的 schema 版本。
RECEIVED_SCHEMA_VERSION :对应租户在对应机器已已经接收到的 RS 发过来的最新刷新任务的 schema 版本。
SCHEMA_COUNT :对应 schema 版本下,各 schema 对象数目的总和( table 数目 + database 数目 +…)。
SCHEMA_SIZE :对应 schema 版本下,各 schema 对象总共所占的内存大小( B )。
obclient> select * from oceanbase.GV$OB_SERVER_SCHEMA_INFO\G*************************** 1. row *************************** SVR_IP: 11.158.31.20 SVR_PORT: 22602 TENANT_ID: 1002 REFRESHED_SCHEMA_VERSION: 1690109029768968 RECEIVED_SCHEMA_VERSION: 1690113309637344 SCHEMA_COUNT: 1583 SCHEMA_SIZE: 1537240MIN_SSTABLE_SCHEMA_VERSION: -11 row in set (0.01 sec)
DDL 和 schema 的问题排查方法
既然都说了这么多,那就再说下 DDL 和 schema 比较常见的几类问题。这一部分欢迎大家补充更好的排查问题方法。
执行 DDL 语法报错了,我该怎么改语法呢?
客户经常会在试着自己把正在用的数据库上的元数据往 OceanBase 开源版本上倒腾,比如前几天见到一个客户希望把 pg 里的分区表定义放到 OceanBase MySQL 模式的租户下执行下,但是报错了,然后就会认为 OceanBase 不支持分区表。
CREATE TABLE value_stream_dashboard_counts (
id bigint NOT NULL,
namespace_id bigint NOT NULL,
count bigint NOT NULL,
metric smallint NOT NULL
)
PARTITION BY RANGE (id);
我们遇到这种问题应该怎么查 OceanBase MySQL 模式下的对应语法呢?大家一般可能会去查各种各样的 OceanBase 语法文档,但是 OceanBase 语法随着兼容性的逐步完善而日新月异,文档内容其实没办法保证和真实支持的语法强一致(甚至连最终一致都不能保证)。想起师兄和我说的一句话:“文档很喜欢骗人,但是代码从不会说谎”,OceanBase 社区版支持的所有语法其实都写在一个叫 sql_parser_mysql_mode.y 的 yacc 文件里。
看完这个文件里的语法规则,我们就很容易把上面那条 SQL 改成 OceanBase MySQL 模式下可以执行成功的 SQL 。
CREATE TABLE value_stream_dashboard_counts (
id bigint NOT NULL,
namespace_id bigint NOT NULL,
count bigint NOT NULL,
metric smallint NOT NULL
)
PARTITION BY RANGE (id)(
PARTITION p0 VALUES LESS THAN (100),
PARTITION p1 VALUES LESS THAN (200),
PARTITION p2 VALUES LESS THAN (300),
PARTITION p3 VALUES LESS THAN MAXVALUE
);
执行 DDL 报了不太明确的错,我该怎么排查失败原因呢?
比如,我执行了一条 DDL,它报错了,报错说我的 check 约束里出现了不允许被包含在 check 约束里的表达式,但具体是什么表达式不被允许?是 c1,是 =,是 sysdate(),还是 c1 = sysdate() ?
obclient> create table t1(c1 int, check (c1 = sysdate()));
ERROR 3814 (HY000): An expression of a check constraint contains disallowed function.
先查一下报错语句的 trace_id 。
select last_trace_id();
+------------------------------------+
| last_trace_id() |
+------------------------------------+
| Y584A0B9E1F14-00060127094761A8-0-0 |
+------------------------------------+
1 row in set (0.00 sec)
那我们就通过 grep Y584A0B9E1F14-00060127094761B0-0-0 observer.log* 去捞下 observer 的日志。
这个 trace 对应的第一条 warning 日志说 :deterministic expr is wrongly specified in CHECK constraint(这条日志其实写的不对,本意应该是 not deterministic expr is wrongly specified in CHECK constraint ),大概意思就是说 check 约束里面有个(非)确定性的表达式,这个是不被允许的。
那么究竟什么表达式是非确定性的表达式呢?这个就需要根据日志里给出的文件和行号 ob_raw_expr_util.cpp:1856 去看一眼代码了,在网页上可以直接跳到具体某个函数的定义里,例如 ObRawExpr::is_non_pure_sys_func_expr 。
这里列出了所有 not deterministic 的表达式,其中就包含我们用到的 sysdate 。
所以我们就可以大概知道 check 约束里的表达式需要保证多次执行都能得到同样的结果吧。像 sysdate 这种输出当前时间的表达式在多个不同的时间执行多次,结果必定是不一样的,所以不允许出现在 check 约束里。这里我们还可以趁机了解下还有哪些表达式属于 not deterministic 的。
执行 DDL 捞不到有用的日志怎么办?
例如我执行了一个创建 database 的 DDL,结果报错了。
obclient> create database xiaofeng_db;
ERROR 4016 (HY000): Internal error
obclient> select last_trace_id();
+------------------------------------+
| last_trace_id() |
+------------------------------------+
| Y584A0B9E1F14-00060127094761B4-0-0 |
+------------------------------------+
1 row in set (0.00 sec)
拿着 trace id 捞日志,grep Y584A0B9E1F14-00060127094761B4-0-0 observer.log*,结果是 rpc error 。
回忆一下刚才说的 DDL 执行过程,DDL arg 会发到 RS 上执行,所以这种情况大概率是在 RS 上执行的时候出了什么幺蛾子,所以我们还需要通过 grep Y584A0B9E1F14-00060127094761B4-0-0 rootservice.log* | vi - 继续 grep 以下 RS 的日志,然后在日志文件里根据错误码 -4016 搜下 ret=-4016 最早出现的地方。
然后我们就可以发现日志里说是在 ob_root_service.cpp 文件的 2887 行报的错,报错原因是:create_database failed, because db_name is forbidden 。这种问题大家先自己根据报错日志里的文件和行号简单分析下原因,如果还是没头绪的话,再找 OceanBase 的技术支持同学协助分析。
翻一下这个文件,哦,原来是是我为了构造在 RS 报错的场景故意在这里加了一个报错的错误码,说只要 create database 的 database_name 叫 xiaofeng_db ,就报错 4016 OB_ERR_UNEXPECTED 。
排查 DDL 和 schema 的问题时忽略 rootservice.log 日志是很常见的情况,曾经亲眼见过很多非常有经验的 OceanBase 内核研发专家不止一次因为这个问题浪费大量时间排查简单一个的小 bug。大家切记这类问题在 observer.log 没线索时,还要去看下 rootservice.log。
刷 schema hang 住了怎么办?
刷 schema hang 住是因为在把内部表的数据加载到内存中的过程中会进行一些 schema 的合法性校验,如果校验失败,就说明持久化在内部表里的元数据信息出问题了,这时 observer 就会 hang 住,什么都干不了。因为一旦元数据都错了,基于错误的元数据无论是执行 DDL、DML,还是执行查询 query ,都是错上加错,很容易产生大量正确性问题。这种情况出现的概率极低,但问题十分严重。
如果执行 DDL hang 住了,并且在 RS 的日志里出现类似于“ Trying so hard to die ” 和 “ schema meta is still not consistent after rebuild , need fixing ” 的信息,表明恢复环境需要人工接入去修改 OceanBase 内部表中的错误信息,风险较高,建议及时找 OceanBase 的技术支持同学帮忙排查问题根因及协助你恢复环境。