未验证 提交 274f8fc5 编写于 作者: O obpilot 提交者: GitHub

Add chapter03 tutorial, about 'how to use oceanbase'. (#538)

* Add chapter03 tutorial, about 'how to use oceanbase'.

* Update some descriptions https://github.com/oceanbase/oceanbase/pull/538

* Replace pictures with urls
上级 2d8ea0b7
# 第 3 章:如何使用 OceanBase 社区版
上一章,我们能够部署 OceanBase 社区版了,这一节课我们开始介绍如何使用 OceanBase 社区版进行业务开发。
## 本章目录
+ [如何查看 OceanBase 集群资源使用情况](3.1.md)
+ [如何创建和连接 MySQL 租户](3.2.md)
+ [如何连接租户](3.3.md)
+ [如何对租户参数(或变量)进行设置](3.4.md)
+ [如何使用 MySQL 租户做常见数据库开发](3.5.md)
+ [如何使用 OceanBase 分区表进行水平拆分](3.6.md)
+ [(高级)如何使用 OceanBase 表分组](3.7.md)
+ [(高级)如何使用 OceanBase 复制表](3.8.md)
+ [常见问题](3.9.md)
+ [附录](3.10.md)
## 如何联系我们
欢迎广大 OceanBase 爱好者、用户和客户有任何问题联系我们反馈:
+ 社区版官网论坛:[https://open.oceanbase.com/answer](https://open.oceanbase.com/answer)
+ 社区版项目网站提 `Issue`[https://github.com/oceanbase/oceanbase/issues](https://github.com/oceanbase/oceanbase/issues)
+ 钉钉群:群号 `33254054`
\ No newline at end of file
# 如何查看 OceanBase 集群资源使用情况
在业务开发之前,DBA 先需要从 OceanBase 集群里创建一个数据库实例出来。这个实例就是 OceanBase 租户。OceanBase 集群可以分配出多个租户,这个能力就是多租户。
## 多租户原理简介
OceanBase 数据库以集群形态部署运行,提供给业务使用的是其中的租户。租户也叫实例,拥有一定的资源能力(如CPU、内存和空间)。
OceanBase 是单进程软件,进程名叫 `observer` ,每个进程启动后,默认会将操作系统的大部分资源据为己有。资源指的是 CPU、内存和磁盘空间。当然,这个资源的使用情况可以通过集群启动参数去设置。
| 参数名 | 参数值 | 参数含义 |
|----------------------------------|-----|------------------------------------------------------------|
| `cpu_count` | 16 | 默认取操作系统的CPU数减去2 。也可以自定义数目。 |
| `memory_limit` | 8G | 进程默认使用的最大内存。如果设置为 0 就是这个参数不限制。跟参数 `memory_limit_percentage` 二选一。 |
| `memory_limit_percentage` | 80 | 进程默认使用的最大内存占总可用内存的百分比。 跟参数 memory_limit 二选一 |
| `datafile_size` | 0 | 进程的数据文件(`block_file`)的初始化大小 。如果设置为 0 就是这个参数不限制。 |
| `datafile_disk_percentage` | 75 | 进程的数据文件(`block_file`)的初始化大小占数据目录(sstable)所在文件系统可用空间大小的百分比。 |
| `clog_disk_usage_limit_percentage` | 95 | 进程的clog 文件所在文件系统空间使用率上限百分比。达到这个值就被认为“空间满”,clog 会停写。 |
进程 `observer` 取得的资源中 CPU 个数是声明式的,内存资源是独占的,磁盘空间是独占的(预分配)。
![oceanbase tenant resources](https://cdn.nlark.com/yuque/0/2021/jpeg/383093/1638762959780-a6bd2389-57b0-4dd6-a0b9-bf5cec53ef78.jpeg)
OceanBase 集群能把所有节点进程 `observer` 取得的资源集中管理,然后从集群中分配出多个租户,每个租户对应一定的资源。这个资源大小可以定义,并且资源可以在线调整,这个功能也是弹性伸缩能力的一个体现。
OceanBase 的租户资源定义是包含 CPU、内存、空间、IOPS 和会话数,目前产品只实现了 CPU 和 内存的资源隔离,空间、IOPS 和会话数不起作用。不过建议创建资源的时候依然要设置的有意义一些。尤其是空间资源,不要定义的超出机器磁盘实际可用空间很多。这个数据会对后期负载均衡机制有一定影响。
## 查看集群可用资源
OceanBase 集群默认有个内部租户(`sys`),可以查看和管理集群的资源。查看集群可用资源请使用下面 SQL。
```sql
select a.zone,concat(a.svr_ip,':',a.svr_port) observer, cpu_total, cpu_assigned, (cpu_total-cpu_assigned) cpu_free, mem_total/1024/1024/1024 mem_total_gb, mem_assigned/1024/1024/1024 mem_assign_gb, (mem_total-mem_assigned)/1024/1024/1024 mem_free_gb
from __all_virtual_server_stat a join __all_server b on (a.svr_ip=b.svr_ip and a.svr_port=b.svr_port)
order by a.zone, a.svr_ip
;
+-------+--------------------+-----------+--------------+----------+----------------+----------------+----------------+
| zone | observer | cpu_total | cpu_assigned | cpu_free | mem_total_gb | mem_assign_gb | mem_free_gb |
+-------+--------------------+-----------+--------------+----------+----------------+----------------+----------------+
| zone1 | 172.20.249.52:2882 | 14 | 2.5 | 11.5 | 5.000000000000 | 1.000000000000 | 4.000000000000 |
| zone2 | 172.20.249.49:2882 | 14 | 2.5 | 11.5 | 5.000000000000 | 1.000000000000 | 4.000000000000 |
| zone3 | 172.20.249.51:2882 | 14 | 2.5 | 11.5 | 5.000000000000 | 1.000000000000 | 4.000000000000 |
+-------+--------------------+-----------+--------------+----------+----------------+----------------+----------------+
3 rows in set (0.008 sec)
```
再查看一下资源分配细节。
```sql
select t1.name resource_pool_name, t2.`name` unit_config_name, t2.max_cpu, t2.min_cpu, t2.max_memory/1024/1024/1024 max_mem_gb, t2.min_memory/1024/1024/1024 min_mem_gb, t3.unit_id, t3.zone, concat(t3.svr_ip,':',t3.`svr_port`) observer,t4.tenant_id, t4.tenant_name
from __all_resource_pool t1 join __all_unit_config t2 on (t1.unit_config_id=t2.unit_config_id)
join __all_unit t3 on (t1.`resource_pool_id` = t3.`resource_pool_id`)
left join __all_tenant t4 on (t1.tenant_id=t4.tenant_id)
order by t1.`resource_pool_id`, t2.`unit_config_id`, t3.unit_id
;
+--------------------+------------------+---------+---------+----------------+----------------+---------+-------+--------------------+-----------+-------------+
| resource_pool_name | unit_config_name | max_cpu | min_cpu | max_mem_gb | min_mem_gb | unit_id | zone | observer | tenant_id | tenant_name |
+--------------------+------------------+---------+---------+----------------+----------------+---------+-------+--------------------+-----------+-------------+
| sys_pool | sys_unit_config | 5 | 5 | 1.500000000000 | 1.500000000000 | 1 | zone1 | 172.20.249.52:2882 | 1 | sys |
| sys_pool | sys_unit_config | 5 | 5 | 1.500000000000 | 1.500000000000 | 2 | zone2 | 172.20.249.49:2882 | 1 | sys |
| sys_pool | sys_unit_config | 5 | 5 | 1.500000000000 | 1.500000000000 | 3 | zone3 | 172.20.249.51:2882 | 1 | sys |
+--------------------+------------------+---------+---------+----------------+----------------+---------+-------+--------------------+-----------+-------------+
3 rows in set (0.006 sec)
```
这个结果显示内部租户的资源池(`resource pool`),由每个`zone` 里的一个节点上的资源单元(`resource unit`)组成,每个资源单元使用同一规格(`sys_unit_config`)。
从上面可以看出,资源单元规格 `sys_unit_config` 的定义里 CPU 和 内存的最小值和最大值定义不一样,前面统计资源里的已分配资源是按最小值计算的。这个会导致剩余可用资源计算不那么直观。
所以,建议把这个资源单元规格定义的 CPU 和内存的最小值和最大值拉平。
```sql
MySQL [oceanbase]> select * from __all_unit_config;
+----------------------------+----------------------------+----------------+-----------------+---------+---------+------------+------------+----------+----------+---------------+---------------------+
| gmt_create | gmt_modified | unit_config_id | name | max_cpu | min_cpu | max_memory | min_memory | max_iops | min_iops | max_disk_size | max_session_num |
+----------------------------+----------------------------+----------------+-----------------+---------+---------+------------+------------+----------+----------+---------------+---------------------+
| 2021-09-12 14:49:43.422194 | 2021-09-13 13:55:55.778987 | 1 | sys_unit_config | 5 | 2.5 | 1610612736 | 1073741824 | 10000 | 5000 | 53687091200 | 9223372036854775807 |
+----------------------------+----------------------------+----------------+-----------------+---------+---------+------------+------------+----------+----------+---------------+---------------------+
1 row in set (0.001 sec)
alter resource unit sys_unit_config min_cpu=5,max_cpu=5,min_memory='1610612736B',max_memory='1610612736B';
```
再查询剩余可用资源就准确了。
```sql
MySQL [oceanbase]> select a.zone,concat(a.svr_ip,':',a.svr_port) observer, cpu_total, cpu_assigned, (cpu_total-cpu_assigned) cpu_free, mem_total/1024/1024/1024 mem_total_gb, mem_assigned/1024/1024/1024 mem_assign_gb, (mem_total-mem_assigned)/1024/1024/1024 mem_free_gb from __all_virtual_server_stat a join __all_server b on (a.svr_ip=b.svr_ip and a.svr_port=b.svr_port) order by a.zone, a.svr_ip;
+-------+--------------------+-----------+--------------+----------+----------------+----------------+----------------+
| zone | observer | cpu_total | cpu_assigned | cpu_free | mem_total_gb | mem_assign_gb | mem_free_gb |
+-------+--------------------+-----------+--------------+----------+----------------+----------------+----------------+
| zone1 | 172.20.249.52:2882 | 14 | 5 | 9 | 5.000000000000 | 1.500000000000 | 3.500000000000 |
| zone2 | 172.20.249.49:2882 | 14 | 5 | 9 | 5.000000000000 | 1.500000000000 | 3.500000000000 |
| zone3 | 172.20.249.51:2882 | 14 | 5 | 9 | 5.000000000000 | 1.500000000000 | 3.500000000000 |
+-------+--------------------+-----------+--------------+----------+----------------+----------------+----------------+
3 rows in set (0.005 sec)
```
计算集群剩余可用资源的目的是为了避免创建业务租户时不会碰到资源不足的报错。
# 附录
## A1 如何修改 MySQL 客户端连接的版本
默认 MySQL 连接到 OceanBase MySQL 租户的时候,会提示 MySQL Version 是 5.6.25 。
```bash
[admin@obce00 ~]$ obclient -h172.20.249.53 -uroot@obmysql#obce-3zones -P2883 -p123456 -c -A oceanbase
Welcome to the OceanBase. Commands end with ; or \g.
Your MySQL connection id is 9
Server version: 5.6.25 OceanBase 3.1.0 (r3-b20901e8c84d3ea774beeaca963c67d7802e4b4e) (Built Aug 10 2021 08:10:38)
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [oceanbase]>
```
由于 MySQL 5.6.25 版本可能存在一些安全漏洞,客户的安全部门可能会认为 OceanBase 也有这个安全问题。OceanBase 的 OBPROXY 和 OBSERVER 已经兼容了 MySQL 5.7 的连接协议,所以可通过 OBPROXY 的管理员账户 `root@proxysys` 直连 OBPROXY 修改一个参数提升这个版本号。
```sql
[admin@obce01 obproxy]$ mysql -h172.20.249.53 -uroot@proxysys -P2883 -p0MdTv1tm -c -A oceanbase
mysql> show proxyconfig like 'mysql_version';
+---------------+--------+--------------------------------------------------------------------------------------------------------------------------+-------------+---------------+
| name | value | info | need_reboot | visible_level |
+---------------+--------+--------------------------------------------------------------------------------------------------------------------------+-------------+---------------+
| mysql_version | 5.6.25 | returned version for mysql mode, default value is 5.6.25. If set, proxy will send new version when user connect to proxy | false | USER |
+---------------+--------+--------------------------------------------------------------------------------------------------------------------------+-------------+---------------+
1 row in set (0.00 sec)
mysql> alter proxyconfig set mysql_version='5.7.1';
Query OK, 0 rows affected (0.00 sec)
mysql> show proxyconfig like 'mysql_version';
+---------------+-------+--------------------------------------------------------------------------------------------------------------------------+-------------+---------------+
| name | value | info | need_reboot | visible_level |
+---------------+-------+--------------------------------------------------------------------------------------------------------------------------+-------------+---------------+
| mysql_version | 5.7.1 | returned version for mysql mode, default value is 5.6.25. If set, proxy will send new version when user connect to proxy | false | USER |
+---------------+-------+--------------------------------------------------------------------------------------------------------------------------+-------------+---------------+
1 row in set (0.00 sec)
```
重新登录就会发现提示的版本变为 `5.7.1`
# 如何创建和连接 MySQL 租户
创建租户分为三步:
+ (可选)创建资源单元规格。如果有合适的规格可以复用,就不用创建了。
+ 创建资源池。可以每个 `zone` 一个资源池,使用独立的资源单元规格,也可以所有 `zone` 使用同一个资源单元规格,都在一个资源池下。
+ 创建租户,关联到这个资源池。
## 创建资源单元规格(`RESOURCE UNIT`)
创建资源单元规格,并不会立即分配资源。资源单元规格元数据在视图 `__all_unit_config` 里。
创建之前可以先查看一下,如果有合适的规格,也是可以复用的。
+ 语法
资源单元规格创建语法如下:
```sql
CREATE RESOURCE UNIT unit_name
MAX_CPU [=] cpu_num,
MAX_MEMORY [=] mem_size,
MAX_IOPS [=] iops_num,
MAX_DISK_SIZE [=] disk_size,
MAX_SESSION_NUM [=] session_num,
[MIN_CPU [=] cpu_num,]
[MIN_MEMORY [=] mem_size,]
[MIN_IOPS [=] iops_num] ;
```
参数解释:
| 参数 | 描述 |
|-----------------|----------------------------------|
| MAX_CPU | 指定 CPU 的最多数量。 |
| MAX_MEMORY | 指定最大内存容量,取值范围为[1073741824, +∞) 。 |
| | 单位为字节B,最小值为 1G。 |
| MAX_IOPS | 指定 IOPS 的最多数量,取值范围为[128,+∞)。 |
| MAX_DISK_SIZE | 指定最大硬盘容量,取值范围为[536870912,+∞]。 |
| | 单位为字节,即最小值为 512M。 |
| MAX_SESSION_NUM | 指定 Session 的最多数量,取值范围为[64,+∞)。 |
| MIN_CPU | 指定 CPU 的最少数量。 |
| MIN_MEMORY | 指定最小内存容量。 |
| MIN_IOPS | 指定 IOPS 的最少数量。 |
+ 示例
```sql
CREATE resource unit S1 max_cpu=3, min_cpu=3, max_memory='3G', min_memory='3G', max_iops=10000, min_iops=1000, max_session_num=1000000, max_disk_size='1024G';
CREATE resource unit S2 max_cpu=4, min_cpu=4, max_memory='3G', min_memory='3G', max_iops=10000, min_iops=1000, max_session_num=1000000, max_disk_size='1024G';
MySQL [oceanbase]> select * from __all_unit_config;
+----------------------------+----------------------------+----------------+-----------------+---------+---------+------------+------------+----------+----------+---------------+---------------------+
| gmt_create | gmt_modified | unit_config_id | name | max_cpu | min_cpu | max_memory | min_memory | max_iops | min_iops | max_disk_size | max_session_num |
+----------------------------+----------------------------+----------------+-----------------+---------+---------+------------+------------+----------+----------+---------------+---------------------+
| 2021-09-12 14:49:43.422194 | 2021-09-13 14:02:08.782027 | 1 | sys_unit_config | 5 | 5 | 1610612736 | 1610612736 | 10000 | 5000 | 53687091200 | 9223372036854775807 |
| 2021-09-14 11:10:51.296235 | 2021-09-14 11:10:51.296235 | 1002 | S1 | 3 | 3 | 3221225472 | 3221225472 | 10000 | 1000 | 1099511627776 | 1000000 |
| 2021-09-14 11:13:46.773639 | 2021-09-14 11:13:46.773639 | 1004 | S2 | 4 | 4 | 3221225472 | 3221225472 | 10000 | 1000 | 1099511627776 | 1000000 |
+----------------------------+----------------------------+----------------+-----------------+---------+---------+------------+------------+----------+----------+---------------+---------------------+
3 rows in set (0.001 sec)
```
## 创建资源池(`RESOURCE POOL`)
资源池从节点的资源中分配。资源池在每个节点里的部分被称之为“资源单元”。每个资源单元的大小通过创建的时候指定“资源单元规格”定。
+ 语法
创建资源池的语法如下:
```sql
CREATE RESOURCE POOL poolname
UNIT [=] unitname,
UNIT_NUM [=] unitnum,
ZONE_LIST [=] ('zone' [, 'zone' ]);
```
参数解释:
| 参数 | 描述 |
|-------------------------------------|-------------------------------------------------------------------------------------------------------------------|
| poolname | 指定要创建的资源池的名称。 |
| UNIT [=] unitname | 指定资源规格的名称。 |
| UNIT_NUM [=] unitnum | 指定要创建的单个 `Zone` 下的 `Unit` 个数。 |
| | 每个单元会根据当前集群负载,自动在每个 `Zone` 中选择一个 `Server` 负载,但同一个资源池的多个 Unit 不能分配到同一个 `Server`,即一个资源池包含的 `Unit` 个数不能超过单 `Zone``Server` 的个数。 |
| ZONE_LIST [=] ('zone' [, 'zone' …]) | 指定要创建的资源池所属的 `Zone`。 如果不指定,就默认在所有 `Zone` 创建这个资源单元。 |
+ 示例
下面例子创建 2个资源池,分别使用不同的资源单元规格。且其中一个资源池横跨两个 `Zone`。 这样用主要是为了演示资源池创建的灵活性。生产环境,为了管理方便,可以一个资源池横跨三个 `Zone`,并且使用同一种资源单元规格。
```sql
create resource pool pool_1 unit='S1' , unit_num=1, zone_list=('zone1' ,'zone2') ;
create resource pool pool_2 unit='S2' , unit_num=1, zone_list=('zone3');
select t1.name resource_pool_name, t2.`name` unit_config_name, t2.max_cpu, t2.min_cpu, t2.max_memory/1024/1024/1024 max_mem_gb, t2.min_memory/1024/1024/1024 min_mem_gb, t3.unit_id, t3.zone, concat(t3.svr_ip,':',t3.`svr_port`) observer,t4.tenant_id, t4.tenant_name
from __all_resource_pool t1 join __all_unit_config t2 on (t1.unit_config_id=t2.unit_config_id)
join __all_unit t3 on (t1.`resource_pool_id` = t3.`resource_pool_id`)
left join __all_tenant t4 on (t1.tenant_id=t4.tenant_id)
order by t1.`resource_pool_id`, t2.`unit_config_id`, t3.unit_id
;
+--------------------+------------------+---------+---------+----------------+----------------+---------+-------+--------------------+-----------+-------------+
| resource_pool_name | unit_config_name | max_cpu | min_cpu | max_mem_gb | min_mem_gb | unit_id | zone | observer | tenant_id | tenant_name |
+--------------------+------------------+---------+---------+----------------+----------------+---------+-------+--------------------+-----------+-------------+
| sys_pool | sys_unit_config | 5 | 5 | 1.500000000000 | 1.500000000000 | 1 | zone1 | 172.20.249.52:2882 | 1 | sys |
| sys_pool | sys_unit_config | 5 | 5 | 1.500000000000 | 1.500000000000 | 2 | zone2 | 172.20.249.49:2882 | 1 | sys |
| sys_pool | sys_unit_config | 5 | 5 | 1.500000000000 | 1.500000000000 | 3 | zone3 | 172.20.249.51:2882 | 1 | sys |
| pool_1 | S1 | 3 | 3 | 3.000000000000 | 3.000000000000 | 1006 | zone1 | 172.20.249.52:2882 | NULL | NULL |
| pool_1 | S1 | 3 | 3 | 3.000000000000 | 3.000000000000 | 1007 | zone2 | 172.20.249.49:2882 | NULL | NULL |
| pool_2 | S2 | 4 | 4 | 3.000000000000 | 3.000000000000 | 1008 | zone3 | 172.20.249.51:2882 | NULL | NULL |
+--------------------+------------------+---------+---------+----------------+----------------+---------+-------+--------------------+-----------+-------------+
6 rows in set (0.037 sec)
select a.zone,concat(a.svr_ip,':',a.svr_port) observer, cpu_total, cpu_assigned, (cpu_total-cpu_assigned) cpu_free, mem_total/1024/1024/1024 mem_total_gb, mem_assigned/1024/1024/1024 mem_assign_gb, (mem_total-mem_assigned)/1024/1024/1024 mem_free_gb
from __all_virtual_server_stat a join __all_server b on (a.svr_ip=b.svr_ip and a.svr_port=b.svr_port)
order by a.zone, a.svr_ip
;
+-------+--------------------+-----------+--------------+----------+----------------+----------------+----------------+
| zone | observer | cpu_total | cpu_assigned | cpu_free | mem_total_gb | mem_assign_gb | mem_free_gb |
+-------+--------------------+-----------+--------------+----------+----------------+----------------+----------------+
| zone1 | 172.20.249.52:2882 | 14 | 8 | 6 | 5.000000000000 | 4.500000000000 | 0.500000000000 |
| zone2 | 172.20.249.49:2882 | 14 | 8 | 6 | 5.000000000000 | 4.500000000000 | 0.500000000000 |
| zone3 | 172.20.249.51:2882 | 14 | 9 | 5 | 5.000000000000 | 4.500000000000 | 0.500000000000 |
+-------+--------------------+-----------+--------------+----------+----------------+----------------+----------------+
3 rows in set (0.026 sec)
```
资源池创建出来后,集群的可用资源就减少了。但是这个资源池还没有关联到具体租户,所以是无法被业务使用到。
## 创建租户(`TENANT`)
租户就是实例,创建租户也是瞬间完成,需要关联到某个资源池。
+ 语法
创建租户的语法如下:
```sql
CREATE TENANT [IF NOT EXISTS] tenant_name
[tenant_characteristic_list] [opt_set_sys_var];
tenant_characteristic_list:
tenant_characteristic [, tenant_characteristic...]
tenant_characteristic:
COMMENT 'string'
| {CHARACTER SET | CHARSET} [=] charsetname
| COLLATE [=] collationname
| REPLICA_NUM [=] num
| ZONE_LIST [=] (zone [, zone])
| PRIMARY_ZONE [=] zone
| DEFAULT TABLEGROUP [=] {NULL | tablegroup}
| RESOURCE_POOL_LIST [=](poolname [, poolname])
| LOGONLY_REPLICA_NUM [=] num
| LOCALITY [=] 'locality description'
opt_set_sys_var:
{SET | SET VARIABLES | VARIABLES} system_var_name = expr [,system_var_name = expr] ...
```
参数解释:
| 参数 | 描述 |
|-------------------------|------------------------------------------------------------------------------------------------------------------------|
| tenant_name | 指定租户名。最长64个字节,只能有大小写英文字母,数字和下划线,而且必须以字母或下划线开头,并且不能是 OceanBase 数据库的关键字。 |
| IF NOT EXISTS | 如果要创建的租户名已存在,并且没有指定 IF NOT EXISTS,则会报错。 |
| RESOURCE_POOL_LIST | 资源池列表,为创建租户时的必填项 。 |
| DEFAULT TABLEGROUP | 设置租户默认表分组信息,NULL 表示取消默认表分组。如果不指定,默认为 NULL。 |
| COMMENT | 修改注释。 |
| CHARACTER SET | CHARSET | 修改租户的字符集。 |
| COLLATE | 指定校对规则。 |
| REPLICA_NUM | 指定副本数。 |
| ZONE_LIST | 指定要修改的 Zone 列表。 |
| PRIMARY_ZONE | 指定 Primary Zone。 |
| LOGONLY_REPLICA_NUM | 指定日志副本数。 |
| LOCALITY | 描述副本在 Zone 间的分布情况,如:F@z1,F@z2,F@z3,R@z4 表示 z1、z2、z3 为全功能副本,z4 为只读副本。 |
| system_var_name | 指定租户系统变量值。其中 `ob_compatibility_mode` 用于指定租户的兼容模式(可选 `mysql``oracle` 。社区版只支持 `mysql` ),只能在创建时指定;如果不指定 `ob_compatibility_mode`,默认兼容模式为 `mysql`。 |
示例:
```sql
create tenant obmysql resource_pool_list=('pool_1','pool_2'), primary_zone='RANDOM',comment 'mysql tenant/instance', charset='utf8' set ob_tcp_invited_nodes='%' ;
MySQL [oceanbase]> select * from gv$tenant;
+-----------+-------------+-------------------+-------------------+----------------+-----------------------+-----------+---------------------------------------------+
| tenant_id | tenant_name | zone_list | primary_zone | collation_type | info | read_only | locality |
+-----------+-------------+-------------------+-------------------+----------------+-----------------------+-----------+---------------------------------------------+
| 1 | sys | zone1;zone2;zone3 | zone1;zone2,zone3 | 0 | system tenant | 0 | FULL{1}@zone1, FULL{1}@zone2, FULL{1}@zone3 |
| 1001 | obmysql | zone1;zone2;zone3 | RANDOM | 0 | mysql tenant/instance | 0 | FULL{1}@zone1, FULL{1}@zone2, FULL{1}@zone3 |
+-----------+-------------+-------------------+-------------------+----------------+-----------------------+-----------+---------------------------------------------+
2 rows in set (0.005 sec)
```
租户创建的时候可以通过 `set ` 命令指定租户变量(参数)值。
# 如何连接租户
OceanBase 开源版的租户只兼容 MYSQL ,连接协议兼容 MYSQL 5.6 。因此 MYSQL 命令行客户端或者图形化工具理论上也是能连接 OceanBase 的租户。此外,OceanBase 也提供专属的命令行客户端工具 OBCLIENT 和 图形化客户端工具 ODC 。
## MYSQL 客户端连接
OceanBase MySQL 租户支持传统 MySQL 客户端连接,连接方式基本不变,跟传统 MySQL 不一样的地方是用户名的格式。
示例:
```bash
mysql -h172.20.249.50 -uroot@sys#obdemo -P2883 -p4S9wDbSr -c -A oceanbase
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 13
Server version: 5.6.25 OceanBase 3.1.0 (r3-b20901e8c84d3ea774beeaca963c67d7802e4b4e) (Built Aug 10 2021 08:10:38)
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [oceanbase]> show databases;
+--------------------+
| Database |
+--------------------+
| oceanbase |
| information_schema |
| mysql |
| SYS |
| LBACSYS |
| ORAAUDITOR |
| test |
+--------------------+
7 rows in set (0.008 sec)
```
备注:
+ `-h`:提供 OceanBase 数据库连接 IP,通常是一个 OBProxy 地址。
+ `-u`:提供租户的连接账户,格式有两种:`用户名@租户名#集群名` 或者 `集群名:租户名:用户名` 。MySQL 租户的管理员用户名默认是 root 。
+ `-P`:提供 OceanBase 数据库连接端口,也是 OBProxy 的监听端口,默认是2883,可以自定义。
+ `-p`:提供账户密码,为了安全可以不提供,改为在后面提示符下输入,密码文本不可见。
+ `-c`:表示在 MySQL 运行环境中不要忽略注释。
+ `-A`:表示在 MySQL 连接数据库时不自动获取统计信息。
oceanbase:访问的数据库名,可以改为业务数据库。
新创建的业务租户的管理员(`root`)密码默认是空的。需要改密码。
```bash
$ strings /dev/urandom |tr -dc A-Za-z0-9 | head -c8; echo
bJVqqEVt
mysql -h172.20.249.50 -uroot@obmysql#obdemo -P2883 -p -c -A oceanbase
MySQL [oceanbase]> alter user root identified by 'bJVqqEVt' ;
Query OK, 0 rows affected (0.118 sec)
```
如果没有安装 MYSQL 客户端,可以安装 `mariadb-server` 。MySQL 官方 8.0 的客户端连接协议在密码处调整了逻辑,导致无法通过早期的 OBPROXY 连接到 OceanBase 的MySQL 租户。会报密码错误。可以通过加选项 `--default-auth=mysql_native_password` 解决这个问题。
```bash
# 安装 mariadb
sudo yum -y install mariadb-server.x86_64
# 或者
# 安装官方 mysql 客户端
sudo yum -y install mysql.x86_64
mysql -h172.20.249.50 -uroot@obmysql#obdemo -P2883 -pbJVqqEVt -c -A --default-auth=mysql_native_password oceanbase
```
注:OBPROXY 2.0 版本修复了这个问题。
## OBCLIENT 客户端连接
OceanBase 还提供专用的命令行客户端工具,名字:`obclient` 。使用方法跟 `mysql` 命令一样。
示例:
```bash
obclient -h172.20.249.50 -uroot@obmysql#obdemo -P2883 -pbJVqqEVt -c -A oceanbase
输出:
Welcome to the OceanBase. Commands end with ; or \g.
Your MySQL connection id is 17
Server version: 5.6.25 OceanBase 3.1.0 (r3-b20901e8c84d3ea774beeaca963c67d7802e4b4e) (Built Aug 10 2021 08:10:38)
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [oceanbase]> show databases;
+--------------------+
| Database |
+--------------------+
| oceanbase |
| information_schema |
| mysql |
| test |
+--------------------+
4 rows in set (0.004 sec)
```
## OceanBase 连接驱动(`JDBC`)
OceanBase 支持的应用目前主要是 `Java``C/C++`
+ Java 语言
MySQL 官方 JDBC 驱动下载地址:[MySQL Connector/J 5.1.46](https://downloads.mysql.com/archives/c-j/)
OceanBase 官方 JDBC 驱动下载地址:[OceanBase-Client](https://help.aliyun.com/document_detail/212815.html)
+ C 语言
具体驱动说明请参考官网文档:[OceanBase Connector/C 简介](https://open.oceanbase.com/docs/connector-c/connector-c/V2.0.0/introduction-to-oceanbase-connector-c)
下载地址:[OceanBase Connector/C 下载](https://github.com/oceanbase/obconnector-c)
## DBEAVER 客户端连接
DBeaver 是一款通用的数据库客户端工具,其原理是使用各个数据库提供的 JDBC 驱动连接数据库,支持常见的关系型数据库、非关系型数据库、分布式数据库等等。使用 OceanBase 提供的 JDBC 驱动 或者 MySQL 官方驱动 ,DBeaver 也可以连接 OceanBase 的MySQL 租户。
官方下载地址:`https://dbeaver.io/download/`
DBeaver 连接 OceanBase 的时候选择“MySQL”数据库类型。第一次使用会自动下载官方 MySQL 驱动。
连接配置页面如下,跟 MySQL 不一样的地方就是连接端口默认是 `2883` 以及 用户名格式是:`用户名@租户名#集群名``集群名:租户名:用户名`
![DBeaver 连接](https://cdn.nlark.com/yuque/0/2021/jpeg/383093/1638764313065-8aecc5eb-cf65-4b1b-9a50-f3a188c7925f.jpeg)
## ODC 客户端连接
OceanBase 提供官方图形化客户端工具 `OceanBase Developer Center`,简称 `ODC`。这个产品在官方网站上有介绍:[什么是 OceanBase 开发者中心](https://www.oceanbase.com/docs/oceanbase-developer-center/odc/V3.1.0/what-is-oceanbase-developer-center)
ODC 下载地址在阿里云上:[下载客户端版 ODC](https://help.aliyun.com/document_detail/212816.html?spm=a2c4g.11186623.6.848.2cb5535fzdJK9X)
![odc new connection](https://cdn.nlark.com/yuque/0/2021/jpeg/383093/1638762959694-eb3aa0ea-4a6a-4c2a-a4d9-d1ddda8cdcb2.jpeg)
![odc new connection2](https://cdn.nlark.com/yuque/0/2021/jpeg/383093/1638762959694-eb3aa0ea-4a6a-4c2a-a4d9-d1ddda8cdcb2.jpeg)
保存连接后,打开连接,即进入工作界面。
![odc new connection3](https://cdn.nlark.com/yuque/0/2021/jpeg/383093/1638762959731-d5853a7c-9714-4bd7-a092-0c7d65f0215f.jpeg)
目前 ODC 是对 OceanBase 适配性最好的客户端工具。
# 如何对租户参数(或变量)进行设置
## 通过 SYS 租户修改业务租户参数
上一课介绍到 OceanBase 集群参数设置,其中有部分参数生效范围是租户(`TENANT`)。在 OceanBase 内部租户(`sys`)里,可以修改业务实例的部分参数。比如说参数 `writing_throttling_trigger_percentage` 用于对指定租户进行内存限流(增量内存使用率达到这个阈值就对写入降速)。
```sql
MySQL [oceanbase]> show parameters like 'writing_throttling_trigger_percentage%'\G
*************************** 1. row ***************************
zone: zone1
svr_type: observer
svr_ip: 172.20.249.50
svr_port: 2882
name: writing_throttling_trigger_percentage
data_type: NULL
value: 100
info: the threshold of the size of the mem store when writing_limit will be triggered. Rang:(0, 100]. setting 100 means turn off writing limit
section: TRANS
scope: TENANT
source: DEFAULT
edit_level: DYNAMIC_EFFECTIVE
1 row in set (0.002 sec)
MySQL [oceanbase]> alter system set writing_throttling_trigger_percentage = 90 tenant='obmysql';
Query OK, 0 rows affected (0.011 sec)
```
这个修改后的值,只能在对应租户里查看。
```bash
[admin@obce00 ~]$ mysql -h 172.20.249.50 -u root@obmysql -P 2881 -p -c -A oceanbase
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 3221538749
Server version: 5.7.25 OceanBase 3.1.0 (r3-b20901e8c84d3ea774beeaca963c67d7802e4b4e) (Built Aug 10 2021 08:10:38)
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [oceanbase]> show parameters like 'writing_throttling_trigger_percentage%'\G
*************************** 1. row ***************************
zone: zone1
svr_type: observer
svr_ip: 172.20.249.50
svr_port: 2882
name: writing_throttling_trigger_percentage
data_type: NULL
value: 90
info: the threshold of the size of the mem store when writing_limit will be triggered. Rang:(0, 100]. setting 100 means turn off writing limit
section: TRANS
scope: TENANT
source: DEFAULT
edit_level: DYNAMIC_EFFECTIVE
1 row in set (0.004 sec)
```
## 修改业务租户参数
当然,在业务租户里,也可以自己设置参数。比如说还有个参数 `writing_throttling_maximum_duration` 是控制增量内存剩余内存根据当前写入速度预估还能支撑多久。这个参数仅供参考,准确性不及上面那个参数。
```sql
# 业务租户里改参数,后面就不需要指定租户名。
MySQL [oceanbase]> alter system set writing_throttling_maximum_duration = '2h';
Query OK, 0 rows affected (0.006 sec)
MySQL [oceanbase]> show parameters like 'writing_throttling_maximum_duration'\G
*************************** 1. row ***************************
zone: zone1
svr_type: observer
svr_ip: 172.20.249.50
svr_port: 2882
name: writing_throttling_maximum_duration
data_type: NULL
value: 2h
info: maximum duration of writing throttling(in minutes), max value is 3 days
section: TRANS
scope: TENANT
source: DEFAULT
edit_level: DYNAMIC_EFFECTIVE
1 row in set (0.004 sec)
```
## 修改业务租户变量
OceanBase 租户还有一个 叫变量(`VARIABLE`)的设计,这个跟 MySQL 实例很像。变量其实就是租户的参数。可以在租户全局层面修改,也可以会话层面修改。很多变量还有对应的 SQL HINT,在语句级别修改。
全局层面的修改影响的是后续的会话,会话层面的修改仅影响当前会话,语句级别的修改只影响当前语句。
OceanBase 租户在刚开始使用的时候,建议调大租户的几个超时参数。
+ `ob_query_timeout` : 语句执行超时时间,单位 `us`, 默认值是 10000000 (也就是 10 秒)。建议根据业务SQL 平均执行时间水平调整。 OLTP场景调整短一些,OLAP场景调整大一些。初学者建议调大 10 倍。
+ `ob_trx_idle_timeout` : 事务空闲超时时间,单位 `us`, 默认值是 120000000(也就是 120 秒)。建议根据业务事务平均空闲时间水平调整。空闲事务会占用连接、可能持有锁不释放,导致高并发时阻塞和死锁概率增加。不建议调大。
+ `ob_trx_timeout` :事务未提交超时时间,单位 `us`,默认值是 100000000 (也就是 100 秒)。建议根据业务事务平均持续时间水平调整。事务长期不提交,会占用连接、可能持有锁不释放,导致高并发时阻塞和死锁概率增加。不建议调大。如果是后台跑批业务,建议在会话级别调大。
+ `ob_trx_lock_timeout`:事务申请加锁等待超时时间,单位 `us`,默认值是 -1(也就是不控制。超时依然会受 `ob_query_timeout` 限制。)。当调大了语句超时时间变量(`ob_query_timeout`)后,可以将这个锁等待超时改为 10000000 (即 10s)。减少阻塞和死锁的概率。
变量查看和修改方法语法:
```sql
show global | session variables like '%变量名部分字段%' ;
set global | session 变量名 = '变量值' ;
```
示例:
```sql
MySQL [oceanbase]> show global variables like '%timeout%';
+---------------------+------------------+
| Variable_name | Value |
+---------------------+------------------+
| connect_timeout | 10 |
| interactive_timeout | 28800 |
| lock_wait_timeout | 31536000 |
| net_read_timeout | 30 |
| net_write_timeout | 60 |
| ob_pl_block_timeout | 3216672000000000 |
| ob_query_timeout | 10000000 |
| ob_trx_idle_timeout | 120000000 |
| ob_trx_lock_timeout | -1 |
| ob_trx_timeout | 100000000 |
| wait_timeout | 28800 |
+---------------------+------------------+
11 rows in set (0.002 sec)
MySQL [oceanbase]> set global ob_query_timeout = 100000000;
Query OK, 0 rows affected (0.015 sec)
MySQL [oceanbase]> set global ob_trx_timeout = 1000000000;
Query OK, 0 rows affected (0.014 sec)
MySQL [oceanbase]> set global ob_trx_idle_timeout = 1200000000;
Query OK, 0 rows affected (0.010 sec)
MySQL [oceanbase]> SET GLOBAL ob_trx_lock_timeout=10000000;
Query OK, 0 rows affected (0.011 sec)
```
对于复杂的 SQL 场景,或者 OLAP 场景,租户还需要调整一个变量 `ob_sql_work_area_percentage` 。这个影响 SQL 里排序统计能利用的内存大小。可以看情况调整。
```sql
set global ob_sql_work_area_percentage=50;
```
## 通过 SYS 租户修改业务租户变量
但是有部分变量属于租户初始化变量,不能在业务租户里直接修改。需要在 SYS 租户里修改。
示例:
```sql
[qing.meiq@xinren-oms.eu95 /home/qing.meiq/mysqldata]
$ mysql -h127.1 -uroot@obmysql#obdemo -P2883 -p123456 -c -A oceanbase -Ns
MySQL [oceanbase]> set global lower_case_table_names=0;
ERROR 1238 (HY000): Variable 'lower_case_table_names' is a read only variable
MySQL [oceanbase]>
[qing.meiq@xinren-oms.eu95 /home/qing.meiq/mysqldata]
$mysql -h127.1 -uroot@sys#obdemo -P2883 -p123456 -c -A oceanbase -Ns
MySQL [oceanbase]> alter tenant obmysql set variables lower_case_table_names=0;
MySQL [oceanbase]>
[qing.meiq@xinren-oms.eu95 /home/qing.meiq/mysqldata]
$ mysql -h127.1 -uroot@obmysql#obdemo -P2883 -p123456 -c -A oceanbase -Ns
MySQL [oceanbase]> show global variables like 'lower_case_table_names';
lower_case_table_names 0
MySQL [oceanbase]>
```
有个变量比较特殊,初始化租户的时候在 SYS 租户设置,后期可以在业务租户里修改。如变量 `ob_tcp_invited_nodes` ,表示租户访问 IP 白名单。
```sql
set global ob_tcp_invited_nodes='11.166.0.0/16,127.0.0.1';
```
如果业务租户设置错误导致无法登录时,还可以通过 SYS 租户再改回正确值。
还有个变量比较特殊,不允许后期更改。如变量 `ob_compatibility_mode` 表示租户兼容性。这个在租户创建时指定,后期不能修改。
# 如何使用 MySQL 租户做常见数据库开发
## 用户管理
这里用户指租户里的用户,跟传统 MySQL 里的用户概念一样。
OceanBase MySQL 租户创建用户有两个方法:
+ `create user` 创建用户。
+ `grant` 语句自动创建用户。
示例:
```sql
MySQL [oceanbase]> create user user01 identified by 'zfkrcOl5MG';
Query OK, 0 rows affected (0.024 sec)
MySQL [oceanbase]> grant all privileges on test.* to user01 ;
Query OK, 0 rows affected (0.013 sec)
MySQL [oceanbase]> grant all privileges on test.* to user02 identified by 'dQuybvRxM8';
Query OK, 0 rows affected (0.028 sec)
```
OceanBase MySQL 租户不支持更新用户元数据的密码字段。
查看用户权限用语句 `show grants`
示例:
```sql
MySQL [oceanbase]> show grants for user01;
+----------------------------------------------+
| Grants for user01@% |
+----------------------------------------------+
| GRANT USAGE ON *.* TO 'user01' |
| GRANT ALL PRIVILEGES ON `test`.* TO 'user01' |
+----------------------------------------------+
2 rows in set (0.001 sec)
MySQL [oceanbase]> show grants for user02;
+----------------------------------------------+
| Grants for user02@% |
+----------------------------------------------+
| GRANT USAGE ON *.* TO 'user02' |
| GRANT ALL PRIVILEGES ON `test`.* TO 'user02' |
+----------------------------------------------+
2 rows in set (0.001 sec)
```
## 数据库管理
OceanBase MySQL 租户下可以建多个数据库(`database`),表只能在具体的数据库下新建。
示例:
```sql
MySQL [test]> create database tpccdb;
Query OK, 1 row affected (0.012 sec)
MySQL [test]> show databases;
+--------------------+
| Database |
+--------------------+
| oceanbase |
| information_schema |
| mysql |
| test |
| tpccdb |
+--------------------+
5 rows in set (0.002 sec)
create table ware(w_id int
, w_ytd decimal(12,2)
, w_tax decimal(4,4)
, w_name varchar(10)
, w_street_1 varchar(20)
, w_street_2 varchar(20)
, w_city varchar(20)
, w_state char(2)
, w_zip char(9)
, unique(w_name, w_city)
, primary key(w_id)
);
create table cust (c_w_id int NOT NULL
, c_d_id int NOT null
, c_id int NOT null
, c_discount decimal(4, 4)
, c_credit char(2)
, c_last varchar(16)
, c_first varchar(16)
, c_middle char(2)
, c_balance decimal(12, 2)
, c_ytd_payment decimal(12, 2)
, c_payment_cnt int
, c_credit_lim decimal(12, 2)
, c_street_1 varchar(20)
, c_street_2 varchar(20)
, c_city varchar(20)
, c_state char(2)
, c_zip char(9)
, c_phone char(16)
, c_since date
, c_delivery_cnt int
, c_data varchar(500)
, index icust(c_last, c_d_id, c_w_id, c_first, c_id)
, FOREIGN KEY (c_w_id) REFERENCES ware(w_id)
, primary key (c_w_id, c_d_id, c_id)
);
```
OceanBase MySQL 租户支持外键。不过在分布式数据库里,如果读写并发很高,不推荐在数据库层面使用外键约束。外键可能会给性能带来负面影响,会增加不必要的阻塞和死锁。
复制表的结构用 `like` ,包括主键、唯一键、索引名称都会复制。在 MySQL 语法里,主键名\唯一约束\索引名在一个表内不能重复,但是不同表之间可以重复。
```sql
create table t1 like ware;
```
复制表的结构和数据用 `create table ... as select` 。不过要注意,这个复制的是表的基本数据类型,对于主键、唯一约束、索引信息等不会复制。
```sql
create table t2 as select * from ware;
MySQL [test]> show create table t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`w_id` int(11) NOT NULL,
`w_ytd` decimal(12,2) DEFAULT NULL,
`w_tax` decimal(4,4) DEFAULT NULL,
`w_name` varchar(10) DEFAULT NULL,
`w_street_1` varchar(20) DEFAULT NULL,
`w_street_2` varchar(20) DEFAULT NULL,
`w_city` varchar(20) DEFAULT NULL,
`w_state` char(2) DEFAULT NULL,
`w_zip` char(9) DEFAULT NULL,
PRIMARY KEY (`w_id`),
UNIQUE KEY `w_name` (`w_name`, `w_city`) BLOCK_SIZE 16384 GLOBAL
) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = COMPACT COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 1 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0
1 row in set (0.003 sec)
MySQL [test]> show create table t2\G
*************************** 1. row ***************************
Table: t2
Create Table: CREATE TABLE `t2` (
`w_id` int(11) NOT NULL,
`w_ytd` decimal(12,2) DEFAULT NULL,
`w_tax` decimal(4,4) DEFAULT NULL,
`w_name` varchar(10) DEFAULT NULL,
`w_street_1` varchar(20) DEFAULT NULL,
`w_street_2` varchar(20) DEFAULT NULL,
`w_city` varchar(20) DEFAULT NULL,
`w_state` char(2) DEFAULT NULL,
`w_zip` char(9) DEFAULT NULL
) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = COMPACT COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 1 BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0
1 row in set (0.002 sec)
```
观察上面两个表结构,主键、唯一键和索引不同。
# 如何使用 OceanBase 分区表进行水平拆分
分区技术(Partitioning)是 OceanBase 非常重要的分布式能力之一,它能解决大表的容量问题和高并发访问时性能问题,主要思想就是将大表拆分为更多更小的结构相同的独立对象,即分区。普通的表只有一个分区,可以看作分区表的特例。每个分区只能存在于一个节点内部,分区表的不同分区可以分散在不同节点上。
## 分区路由
OceanBase 的分区表是内建功能,您只需要在建表的时候指定分区策略和分区数即可。分区表的查询 SQL 跟普通表是一样的,OceanBase 的 OBProxy 或 OBServer 会自动将用户 SQL 路由到相应节点内,因此,分区表的分区细节对业务是透明的。
如果知道要读取的数据所在的分区号,可以通过 SQL 直接访问分区表的某个分区。简单语法格式如下:
```sql
part_table partition ( p[0,1,][sp[0,1,...]] )
```
默认情况下,除非表定义了分区名,分区名都是按一定规则编号,例如:
一级分区名为:p0 , p1 , p2 , …
二级分区名为:p0sp0 , p0sp1 , p0sp2 , … ; p1sp0 , p1sp1 , p1sp2 , …
示例:访问分区表的具体分区。
```sql
select * from t1 partition (p0) ;
select * from t1 partition (p5sp0) ;
```
## 分区策略
OceanBase 支持多种分区策略:
+ 范围(`RANGE`)分区
+ `RANGE COLUMNS` 分区
+ 列表(`LIST`)分区
+ `LIST COLUMNS` 分区
+ 哈希(`HASH`)分区
+ 组合分区
## 范围(`RANGE`)分区
RANGE 分区根据分区表定义时为每个分区建立的分区键值范围,将数据映射到相应的分区中。它是常见的分区类型,经常跟日期类型一起使用。比如说,可以将业务日志表按日/周/月分区。RANGE 分区简单的语法格式如下:
```sql
CREATE TABLE table_name (
column_name1 column_type
[, column_nameN column_type]
) PARTITION BY RANGE ( expr(column_name1) )
(
PARTITION p0 VALUES LESS THAN ( expr )
[, PARTITION pN VALUES LESS THAN (expr ) ]
[, PARTITION pX VALUES LESS THAN (maxvalue) ]
);
```
当使用 RANGE 分区时,需要遵守如下几个规则:
+ `PARTITION BY RANGE ( expr )` 里的 expr 表达式的结果必须为整形。
+ 每个分区都有一个 VALUES LESS THAN 子句,它为分区指定一个非包含的上限值。分区键的任何值等于或大于这个值时将被映射到下一个分区中。
+ 除第一个分区外,所有分区都隐含一个下限值,即上一个分区的上限值。
+ 允许且只允许最后一个分区上限定义为 MAXVALUE ,这个值没有具体的数值,比其他所有分区的上限都要大,也包含空值。
**注意**:RANGE 分区可以新增、删除分区。如果最后一个 RANGE 分区指定了 `MAXVALUE` ,则不能新增分区。所以建议不要使用 `MAXVALUE` 定义最后一个分区。
`RANGE` 分区要求表拆分键表达式的结果必须为整型,如果要按时间类型列做 `RANGE` 分区,则必须使用 `timestamp` 类型,并且使用函数 `UNIX_TIMESTAMP` 将时间类型转换为数值。这个需求也可以使用 `RANGE COLUMNS` 分区实现,就没有整型这个要求。
示例如下:
```sql
CREATE TABLE test_range(id INT, gmt_create TIMESTAMP, info VARCHAR(20), PRIMARY KEY (gmt_create))
PARTITION BY RANGE(UNIX_TIMESTAMP(gmt_create))
(PARTITION p0 VALUES LESS THAN (UNIX_TIMESTAMP('2015-01-01 00:00:00')),
PARTITION p1 VALUES LESS THAN (UNIX_TIMESTAMP('2016-01-01 00:00:00')),
PARTITION p2 VALUES LESS THAN (UNIX_TIMESTAMP('2017-01-01 00:00:00')));
```
## 哈希(`HASH`)分区
HASH 分区适合于对不能用 RANGE 分区、LIST 分区方法的场景,它的实现方法简单,通过对分区键上的 HASH 函数值来散列记录到不同分区中。如果您的数据符合下列特点,使用 HASH 分区是个很好的选择:
+ 不能指定数据的分区键的列表特征。
+ 不同范围内的数据大小相差非常大,并且很难手动调整均衡。
+ 使用 RANGE 分区后数据聚集严重。
+ 并行 DML、分区剪枝和分区连接等性能非常重要。
示例:创建一个 `HASH` 分区表
```sql
CREATE TABLE ware2(
w_id int
, w_ytd number(12,2)
, w_tax number(4,4)
, w_name varchar(10)
, w_street_1 varchar(20)
, w_street_2 varchar(20)
, w_city varchar(20)
, w_state char(2)
, w_zip char(9)
, primary key(w_id)
) PARTITION by hash(w_id) partitions 60;
```
HASH 分区不能做新增或删除分区操作。
## 组合分区(二级分区)
组合分区通常是先使用一种分区策略,然后在子分区再使用另外一种分区策略,适合于业务表的数据量非常大时。使用组合分区能发挥多种分区策略的优点。
在指定二级分区分区策略细节时,可以使用 SUBPARTITION TEMPLATE 子句。
示例:创建组合分区表。
```sql
CREATE TABLE t_ordr_part_by_hash_range (o_w_id int
, o_d_id int
, o_id int
, o_c_id int
, o_carrier_id int
, o_ol_cnt int
, o_all_local int
, o_entry_d TIMESTAMP NOT NULL
, index idx_ordr(o_w_id, o_d_id, o_c_id, o_id) LOCAL
, primary key ( o_w_id, o_d_id, o_id, o_entry_d )
)
PARTITION BY hash(o_w_id)
SUBPARTITION BY RANGE(UNIX_TIMESTAMP(o_entry_d))
SUBPARTITION template
(
SUBPARTITION M202001 VALUES LESS THAN(UNIX_TIMESTAMP('2020/02/01'))
, SUBPARTITION M202002 VALUES LESS THAN(UNIX_TIMESTAMP('2020/03/01'))
, SUBPARTITION M202003 VALUES LESS THAN(UNIX_TIMESTAMP('2020/04/01'))
, SUBPARTITION M202004 VALUES LESS THAN(UNIX_TIMESTAMP('2020/05/01'))
, SUBPARTITION M202005 VALUES LESS THAN(UNIX_TIMESTAMP('2020/06/01'))
, SUBPARTITION M202006 VALUES LESS THAN(UNIX_TIMESTAMP('2020/07/01'))
, SUBPARTITION M202007 VALUES LESS THAN(UNIX_TIMESTAMP('2020/08/01'))
, SUBPARTITION M202008 VALUES LESS THAN(UNIX_TIMESTAMP('2020/09/01'))
, SUBPARTITION M202009 VALUES LESS THAN(UNIX_TIMESTAMP('2020/10/01'))
, SUBPARTITION M202010 VALUES LESS THAN(UNIX_TIMESTAMP('2020/11/01'))
, SUBPARTITION M202011 VALUES LESS THAN(UNIX_TIMESTAMP('2020/12/01'))
, SUBPARTITION M202012 VALUES LESS THAN(UNIX_TIMESTAMP('2021/01/01'))
)
partitions 16;
CREATE TABLE t_log_part_by_range_hash (
log_id int NOT NULL
, log_value varchar(50)
, log_date TIMESTAMP NOT NULL
, PRIMARY key(log_id, log_date)
) PARTITION BY RANGE(UNIX_TIMESTAMP(log_date))
SUBPARTITION BY HASH(log_id) SUBPARTITIONS 16
(
PARTITION M202001 VALUES LESS THAN(UNIX_TIMESTAMP('2020/02/01'))
, PARTITION M202002 VALUES LESS THAN(UNIX_TIMESTAMP('2020/03/01'))
, PARTITION M202003 VALUES LESS THAN(UNIX_TIMESTAMP('2020/04/01'))
, PARTITION M202004 VALUES LESS THAN(UNIX_TIMESTAMP('2020/05/01'))
, PARTITION M202005 VALUES LESS THAN(UNIX_TIMESTAMP('2020/06/01'))
, PARTITION M202006 VALUES LESS THAN(UNIX_TIMESTAMP('2020/07/01'))
, PARTITION M202007 VALUES LESS THAN(UNIX_TIMESTAMP('2020/08/01'))
, PARTITION M202008 VALUES LESS THAN(UNIX_TIMESTAMP('2020/09/01'))
, PARTITION M202009 VALUES LESS THAN(UNIX_TIMESTAMP('2020/10/01'))
, PARTITION M202010 VALUES LESS THAN(UNIX_TIMESTAMP('2020/11/01'))
, PARTITION M202011 VALUES LESS THAN(UNIX_TIMESTAMP('2020/12/01'))
, PARTITION M202012 VALUES LESS THAN(UNIX_TIMESTAMP('2021/01/01'))
);
```
尽管 OceanBase 在组合分区上支持 `RANGE + HASH``HASH + RANGE` 两种组合,对于一个流水大表,为了维护方便(新增和删除分区),建议使用 `RANGE + HASH` 组合方式。
## 分区表的索引
分区表的查询性能跟 SQL 中条件有关。当 SQL 中带上拆分键时,OceanBase 会根据条件做分区剪枝,只用搜索特定的分区即可;如果没有拆分键,则要扫描所有分区。
分区表也可以通过创建索引来提升性能。跟分区表一样,分区表的索引也可以分区或者不分区。
+ 如果分区表的索引不分区,就是一个全局索引( `GLOBAL` ),是一个独立的分区,索引数据覆盖整个分区表。
+ 如果分区表的索引分区了,根据分区策略又可以分为两类。一是跟分区表保持一致的分区策略,则每个索引分区的索引数据覆盖相应的分区表的分区,这个索引又叫本地索引( `LOCAL` )。
**注意**:通常创建索引时默认都是全局索引,本地索引需要在后面增加关键字 LOCAL 。
建议尽可能的使用本地索引,只有在有必要的时候才使用全局索引。其原因是全局索引会降低 DML 的性能,DML 可能会因此产生分布式事务。
示例:创建分区表的本地索引和全局索引。
```sql
CREATE INDEX idx_log_date ON t_log_part_by_range_hash(log_date) LOCAL;
CREATE INDEX idx_log_date2 ON t_log_part_by_range_hash(log_value, log_date) GLOBAL;
```
**注意**:OceanBase 的分区表主键和唯一键,不需要单独建索引。OceanBase 分区表的一个功能限制是如果分区表有主键,主键必须包含分区键。
## 分区表的实践
通常当表的数据量非常大,以致于可能使数据库空间紧张,或者由于表非常大导致相关 SQL 查询性能变慢时,可以考虑使用分区表。
使用分区表时要选择合适的拆分键以及拆分策略。如果是日志类型的大表,根据时间类型的列做 RANGE 分区是最合适的。如果是并发访问非常高的表,结合业务特点选择能满足绝大部分核心业务查询的列作为拆分键是最合适的。无论选哪个列做为分区键,都不大可能满足所有的查询性能。
分区表中的全局唯一性需求可以通过主键约束和唯一约束实现。OceanBase 数据库的分区表的主键约束必须包含拆分键。唯一约束是一个全局索引。全局唯一的需求也可以通过本地唯一索引实现(在唯一索引里包含拆分键 ),也可以通过全局索引实现。
```sql
CREATE TABLE account(
id bigint NOT NULL PRIMARY KEY
, name varchar(50) NOT NULL UNIQUE
, value number NOT NULL
, gmt_create timestamp DEFAULT current_timestamp NOT NULL
, gmt_modified timestamp DEFAULT current_timestamp NOT NULL
) PARTITION BY HASH(id) PARTITIONS 16;
CREATE TABLE account2(
id bigint NOT NULL PRIMARY KEY
, name varchar(50) NOT NULL
, value number NOT NULL
, gmt_create timestamp DEFAULT current_timestamp NOT NULL
, gmt_modified timestamp DEFAULT current_timestamp NOT NULL
) PARTITION BY HASH(id) PARTITIONS 16;
CREATE UNIQUE INDEX account2_uk ON account2(name, id) LOCAL ;
CREATE UNIQUE INDEX t_log_part_by_range_hash_uk2 on t_log_part_by_range_hash(log_value);
obclient> show indexes from account;
+---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+-----------+---------------+---------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible |
+---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+-----------+---------------+---------+
| account | 0 | PRIMARY | 1 | id | A | NULL | NULL | NULL | | BTREE | available | | YES |
| account | 0 | name | 1 | name | A | NULL | NULL | NULL | | BTREE | available | | YES |
+---------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+-----------+---------------+---------+
2 rows in set (0.002 sec)
obclient> show indexes from account2;
+----------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+-----------+---------------+---------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible |
+----------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+-----------+---------------+---------+
| account2 | 0 | PRIMARY | 1 | id | A | NULL | NULL | NULL | | BTREE | available | | YES |
| account2 | 0 | account2_uk | 1 | name | A | NULL | NULL | NULL | | BTREE | available | | YES |
| account2 | 0 | account2_uk | 2 | id | A | NULL | NULL | NULL | | BTREE | available | | YES |
+----------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+-----------+---------------+---------+
3 rows in set (0.002 sec)
obclient> SELECT * FROM information_schema.`TABLE_CONSTRAINTS` WHERE table_schema='TEST' AND table_name LIKE 'ACCOUNT%';
+--------------------+-------------------+-----------------+--------------+------------+-----------------+
| CONSTRAINT_CATALOG | CONSTRAINT_SCHEMA | CONSTRAINT_NAME | TABLE_SCHEMA | TABLE_NAME | CONSTRAINT_TYPE |
+--------------------+-------------------+-----------------+--------------+------------+-----------------+
| def | test | PRIMARY | test | account | PRIMARY KEY |
| def | test | name | test | account | UNIQUE |
| def | test | PRIMARY | test | account2 | PRIMARY KEY |
| def | test | account2_uk | test | account2 | UNIQUE |
+--------------------+-------------------+-----------------+--------------+------------+-----------------+
4 rows in set (0.001 sec)
```
# (高级)如何使用 OceanBase 表分组
## 表分组简介
表分组(`TABLE GROUP`)是 OceanBase 作为分布式数据库的一个特色功能。
表分组是表的属性,会影响多个表的分区在 OceanBase 机器上的分布特征。
不同表的分区有可能分布在不同的节点上,当两个表做表连接查询时,OceanBase 会跨节点请求数据,执行时间就跟节点间请求延时有关。在 SQL 调优时,OceanBase 建议对业务上关系密切的表,设置相同的表分组。OceanBase 对于同一个表分组中的表的同号分区会管理为一个分区组。同一个分区组中的分区,OceanBase 会尽可能的分配到同一个节点内部,这样就可以规避跨节点的请求。
## 创建表分组
创建表分组时,首先要规划好表分组的用途。如果是用于普通表的属性,表分组就不用分区;如果是用于分区表的属性,表分组就要指定分区策略,并且要跟分区表的分区策略保持一致。
示例:创建表分组和查看表分组。
```sql
create tablegroup tpcc_group partition by hash partitions 6 ;
MySQL [test]> show tablegroups;
+-----------------+------------+---------------+
| Tablegroup_name | Table_name | Database_name |
+-----------------+------------+---------------+
| oceanbase | NULL | NULL |
| tpcc_group | NULL | NULL |
+-----------------+------------+---------------+
2 rows in set (0.004 sec)
MySQL [test]> show create tablegroup tpcc_group;
+------------+-------------------------------------------------------------------------------------------------+
| Tablegroup | Create Tablegroup |
+------------+-------------------------------------------------------------------------------------------------+
| tpcc_group | CREATE TABLEGROUP IF NOT EXISTS `tpcc_group` BINDING = FALSE
partition by hash partitions 6
|
+------------+-------------------------------------------------------------------------------------------------+
1 row in set (0.001 sec)
```
查看表分组下的表的语句是:`show tablegroups ; `
有了表分组后,在建表时就可以指定表分组。
```sql
create table ordr (
o_w_id int
, o_d_id int
, o_id int
, o_c_id int
, o_carrier_id int
, o_ol_cnt int
, o_all_local int
, o_entry_d date
, index iordr(o_w_id, o_d_id, o_c_id, o_id) local
, primary key ( o_w_id, o_d_id, o_id )
)tablegroup tpcc_group partition by hash(o_w_id) partitions 6;
create table ordl (
ol_w_id int
, ol_d_id int
, ol_o_id int
, ol_number int
, ol_delivery_d date
, ol_amount decimal(6, 2)
, ol_i_id int
, ol_supply_w_id int
, ol_quantity int
, ol_dist_info char(24)
, primary key (ol_w_id, ol_d_id, ol_o_id, ol_number )
)tablegroup tpcc_group partition by hash(ol_w_id) partitions 6;
MySQL [test]> show tablegroups;
+-----------------+------------+---------------+
| Tablegroup_name | Table_name | Database_name |
+-----------------+------------+---------------+
| oceanbase | NULL | NULL |
| tpcc_group | ordl | test |
| tpcc_group | ordr | test |
+-----------------+------------+---------------+
3 rows in set (0.004 sec)
```
也可以后期将一个表加入到表分组,使用语句:`alter tablegroup ... add `
将表从表分组中移出时,使用语句:`alter table ... tablegroup = ''; `
```sql
MySQL [test]> alter table ordl tablegroup = '';
Query OK, 0 rows affected (0.148 sec)
MySQL [test]> alter table ordr tablegroup = '';
Query OK, 0 rows affected (0.018 sec)
MySQL [test]> show tablegroups;
+-----------------+------------+---------------+
| Tablegroup_name | Table_name | Database_name |
+-----------------+------------+---------------+
| oceanbase | NULL | NULL |
| tpcc_group | NULL | NULL |
+-----------------+------------+---------------+
2 rows in set (0.004 sec)
MySQL [test]> alter tablegroup tpcc_group add ordl , ordr ;
Query OK, 0 rows affected (0.016 sec)
MySQL [test]> show tablegroups;
+-----------------+------------+---------------+
| Tablegroup_name | Table_name | Database_name |
+-----------------+------------+---------------+
| oceanbase | NULL | NULL |
| tpcc_group | ordl | test |
| tpcc_group | ordr | test |
+-----------------+------------+---------------+
3 rows in set (0.004 sec)
```
# (高级)如何使用 OceanBase 复制表
## 复制表原理
复制表指的是一种特殊的表。
普通的表在生产环境,默认有三副本,其中一个主副本和两个备副本。备副本通过同步主副本的事务日志 `clog` 保持同步,同步协议是 Paxos 协议,主副本的事务日志只有在多数成员里确认落盘后,事务修改才会生效。
通常,默认情况下,读写都是在主副本上,备副本是不提供读写服务。应用如果开启会话或语句级别的弱一致性读后,备副本可能会提供只读服务。风险就是备副本的读会有些许延迟。
普通表可以变为复制表,然后主副本和所有备副本之间使用全同步协议,主副本的事务日志只有在所有副本成员里确认落盘后,事务修改才会生效。所以主副本跟所有备副本的数据理论上都是强一致的。
## 复制表场景
传统普通的表,主副本可能会成为读写瓶颈,业务可能会使用读写分离技术,将只读查询分离出去。运维将只读查询路由到备副本。这个风险是备副本理论上有一定延时。如果表是复制表,则备副本没有延迟问题。这是一种使用场景,前提是开启弱一致性读。
复制表最有用的场景是业务数据库做了水平拆分后,有部分业务表不适合拆分。前者的数据主副本有可能在所有机器上,后者的主副本只会在某台机器上。OceanBase 里一个事务的 SQL 都会跟随到事务开始时那条 SQL 的路由,如果某个 SQL 被路由到的节点不是该 SQL 访问的分区的主副本节点,这个 SQL 就是个远程 SQL 。如果这个分区所在的表是复制表,则这条 SQL 就会在本机执行,从而提升性能。
复制表使用的前提是表的修改频率不能太高,每个事务的平均延时会比普通的表的事务延时要大。
## 复制表语法
可以在创建表的时候就指定复制表属性 `DUPLICATE_SCOPE` 。这个属性有下面几个值:
+ `NONE` : 这个是默认值,表示是普通的表。
+ `CLUSTER` :表的备副本分布在租户资源池所在的所有机器上。
示例:
```sql
mysql> create table t1(id bigint not null auto_increment , c1 varchar(50), c2 timestamp not null default current_timestamp) duplicate_scope='cluster' ; Query OK, 0 rows affected (0.12 sec)
```
也可以在表创建好后修改这个属性。
示例:
```sql
mysql> alter table t1 duplicate_scope = 'NONE';
Query OK, 0 rows affected (0.05 sec)
mysql> alter table t1 duplicate_scope = 'CLUSTER';
Query OK, 0 rows affected (0.04 sec)
```
# 常见问题
## 租户创建或扩容提示机器资源不足
+ 现象
在创建资源池的时候,或者在调整资源池资源规格的时候,碰到报错信息如下:
```sql
ERROR 4624 (HY000): machine resource 'zone1' is not enough to hold a new unit
```
+ 原因
报错信息提示了某个 ZONE 里没有足够的资源创建该资源单元。通常是资源单元规格超出了集群资源可用资源。
所以创建资源池或者调整资源池之前需要计算集群可用资源。这个只需要查看视图 `__all_virtual_server_stat`
由于默认 SYS 租户的资源规格的 `min_cpu``min_memory` 跟对应的 `max_cpu``max_memory` 不一致,会导致集群剩余资源展示的不准。
查看剩余资源的 SQL:
```sql
select a.zone,concat(a.svr_ip,':',a.svr_port) observer, cpu_total, cpu_assigned, (cpu_total-cpu_assigned) cpu_free, mem_total/1024/1024/1024 mem_total_gb, mem_assigned/1024/1024/1024 mem_assign_gb, (mem_total-mem_assigned)/1024/1024/1024 mem_free_gb
from __all_virtual_server_stat a join __all_server b on (a.svr_ip=b.svr_ip and a.svr_port=b.svr_port)
order by a.zone, a.svr_ip
;
```
注意这个结果都是四舍五入的,会对结果有一点影响。保持所有资源的分配不要有小数点,则这个结果就准确了。
## 建表提示机器资源不足
+ 现象
普通的建表语句,忽然报错提示机器资源不足。
```sql
MySQL [test]> create table t01(id bigint not null primary key auto_increment, c1 varchar(50), c2 datetime not null default current_timestamp);
ERROR 4624 (HY000): machine resource is not enough to hold a new unit
```
+ 原因
在三副本集群里,默认建表会创建三个副本。如果有节点掉线,并且该租户在那个节点上还有资源单元(`resource unit`)存在,则这个建表语句就无法创建三个副本。默认情况下,OceanBase 为了保证表元数据强一致,就会报错。这个报错信息是跟机器资源有关,但是不是那么直接。但是可以通过修改租户参数关闭这个强约束。
参数名是:`ob_create_table_strict_mode`
```sql
MySQL [test]> show global variables like '%strict%';
+-----------------------------+-------+
| Variable_name | Value |
+-----------------------------+-------+
| ob_create_table_strict_mode | ON |
+-----------------------------+-------+
1 row in set (0.009 sec)
```
+ 解决方法
方法一:找出租户的资源单元所在节点,查看该节点掉线的原因,解决它,然后建表。这个时间可能有点长。
方法二:会话级别或者全局级别关闭参数 `ob_create_table_strict_mode` ,允许节点掉线情况下建表能成功。
```sql
MySQL [test]> set session ob_create_table_strict_mode=off;
Query OK, 0 rows affected (0.001 sec)
MySQL [test]> create table t01(id bigint not null primary key auto_increment, c1 varchar(50), c2 datetime not null default current_timestamp);
Query OK, 0 rows affected (0.071 sec)
MySQL [test]> desc t01;
+-------+-------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+-------------------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| c1 | varchar(50) | YES | | NULL | |
| c2 | datetime | NO | | CURRENT_TIMESTAMP | |
+-------+-------------+------+-----+-------------------+----------------+
3 rows in set (0.011 sec)
```
注意:节点异常还是要尽快修复。方法二理论上还是有风险的。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册