# OBPROXY SQL 路由原理

OBPROXY 的主要功能就是提供 SQL 路由。所以，首先要了解数据的位置在哪里，然后了解 SQL 路由策略。

## 查看租户资源单元位置

从前面课程知租户的资源通常是分布在每个 ZONE 里的机器上。租户的数据就在租户资源所在的机器上。集群规模很大的时候，每个租户可能只是在部分机器上。OBPROXY 首先需要知道租户的位置（`LOCATION CACHE`）。下面有 2 种方法确认租户位置。

+ SYS 租户直接查询

```sql
select t1.name resource_pool_name, t2.`name` unit_config_name, t2.max_cpu, t2.min_cpu, round(t2.max_memory/1024/1024/1024) max_mem_gb, round(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 |          2 |          2 |       1 | zone1 | 172.20.249.52:2882 |         1 | sys         |
| sys_pool           | sys_unit_config  |       5 |       5 |          2 |          2 |       2 | zone2 | 172.20.249.49:2882 |         1 | sys         |
| sys_pool           | sys_unit_config  |       5 |       5 |          2 |          2 |       3 | zone3 | 172.20.249.51:2882 |         1 | sys         |
| my_pool_zone1      | unit1            |       9 |       9 |         19 |         19 |    1001 | zone1 | 172.20.249.52:2882 |      1002 | obmysql     |
| my_pool_zone2      | unit1            |       9 |       9 |         19 |         19 |    1002 | zone2 | 172.20.249.49:2882 |      1002 | obmysql     |
| my_pool_zone3      | unit1            |       9 |       9 |         19 |         19 |    1003 | zone3 | 172.20.249.51:2882 |      1002 | obmysql     |
+--------------------+------------------+---------+---------+------------+------------+---------+-------+--------------------+-----------+-------------+
6 rows in set (0.008 sec)

```

+ 业务租户直接查询

```sql
select tenant_name, unit_id, unit_config_name, resource_pool_name, zone, svr_ip ,max_cpu,min_cpu,round(max_memory/1024/1024/1024) max_mem_gb,round(min_memory/1024/1024/1024) min_mem_gb
from gv$unit 
WHERE tenant_id=1002;

输出：
+-------------+---------+------------------+--------------------+-------+---------------+---------+---------+------------+------------+
| tenant_name | unit_id | unit_config_name | resource_pool_name | zone  | svr_ip        | max_cpu | min_cpu | max_mem_gb | min_mem_gb |
+-------------+---------+------------------+--------------------+-------+---------------+---------+---------+------------+------------+
| obmysql     |    1001 | unit1            | my_pool_zone1      | zone1 | 172.20.249.52 |       9 |       9 |         19 |         19 |
| obmysql     |    1002 | unit1            | my_pool_zone2      | zone2 | 172.20.249.49 |       9 |       9 |         19 |         19 |
| obmysql     |    1003 | unit1            | my_pool_zone3      | zone3 | 172.20.249.51 |       9 |       9 |         19 |         19 |
+-------------+---------+------------------+--------------------+-------+---------------+---------+---------+------------+------------+
3 rows in set (0.031 sec)

```

在业务租户里，条件 `tenant_id=1002` 也可以不用，因为每个业务租户只能查看自己的租户资源单元信息。

直连 OBSERVER 的时候，如果该 OBSERVER 节点上没有这个租户的资源单元，连接会报错。

## 查看分区副本位置

每个租户的分区都是从租户的资源单元中分配的。
下面的 SQL 可以在 SYS 租户里查看租户中所有数据库的主副本位置。

```sql
SELECT  a.tenant_name, t.table_name, d.database_name,  tg.tablegroup_name , t.part_num , t2.partition_id, t2.ZONE, t2.svr_ip , round(t2.data_size/1024/1024/1024) data_size_gb
 , IF(t.primary_zone = '' OR t.primary_zone IS NULL, a.primary_zone, t.primary_zone) primary_zone
FROM oceanbase.__all_tenant AS a  
 JOIN oceanbase.__all_virtual_database AS d ON ( a.tenant_id = d.tenant_id  ) 
 JOIN oceanbase.__all_virtual_table AS t  ON (t.tenant_id = d.tenant_id AND t.database_id = d.database_id) 
 JOIN oceanbase.__all_virtual_meta_table t2 ON (t.tenant_id = t2.tenant_id AND (t.table_id=t2.table_id OR t.tablegroup_id=t2.table_id)  AND t2.ROLE IN (1) )
 LEFT JOIN oceanbase.__all_virtual_tablegroup AS tg ON (t.tenant_id = tg.tenant_id and  t.tablegroup_id = tg.tablegroup_id) 
WHERE a.tenant_id IN (1002 ) AND  t.table_type IN (3)  
 AND d.database_name IN ( 'tpccdb')
ORDER BY t.tenant_id, tg.tablegroup_name, d.database_name, t.table_name, t2.partition_id
;

+-------------+------------------+---------------+-----------------+----------+--------------+-------+---------------+--------------+-------------------+
| tenant_name | table_name       | database_name | tablegroup_name | part_num | partition_id | ZONE  | svr_ip        | data_size_gb | primary_zone      |
+-------------+------------------+---------------+-----------------+----------+--------------+-------+---------------+--------------+-------------------+
| obmysql     | bmsql_config     | tpccdb        | NULL            |        1 |            0 | zone2 | 172.20.249.49 |            0 | zone2;zone1,zone3 |
| obmysql     | bmsql_item       | tpccdb        | NULL            |        1 |            0 | zone1 | 172.20.249.52 |            0 | zone1;zone2,zone3 |
| obmysql     | bmsql_customer   | tpccdb        | tpcc_group      |        3 |            0 | zone2 | 172.20.249.49 |            0 | RANDOM            |
| obmysql     | bmsql_customer   | tpccdb        | tpcc_group      |        3 |            1 | zone1 | 172.20.249.52 |            0 | RANDOM            |
| obmysql     | bmsql_customer   | tpccdb        | tpcc_group      |        3 |            2 | zone3 | 172.20.249.51 |            0 | RANDOM            |
| obmysql     | bmsql_district   | tpccdb        | tpcc_group      |        3 |            0 | zone2 | 172.20.249.49 |            0 | RANDOM            |
| obmysql     | bmsql_district   | tpccdb        | tpcc_group      |        3 |            1 | zone1 | 172.20.249.52 |            0 | RANDOM            |
| obmysql     | bmsql_district   | tpccdb        | tpcc_group      |        3 |            2 | zone3 | 172.20.249.51 |            0 | RANDOM            |
| obmysql     | bmsql_history    | tpccdb        | tpcc_group      |        3 |            0 | zone2 | 172.20.249.49 |            0 | RANDOM            |
| obmysql     | bmsql_history    | tpccdb        | tpcc_group      |        3 |            1 | zone1 | 172.20.249.52 |            0 | RANDOM            |
| obmysql     | bmsql_history    | tpccdb        | tpcc_group      |        3 |            2 | zone3 | 172.20.249.51 |            0 | RANDOM            |
| obmysql     | bmsql_new_order  | tpccdb        | tpcc_group      |        3 |            0 | zone2 | 172.20.249.49 |            0 | RANDOM            |
| obmysql     | bmsql_new_order  | tpccdb        | tpcc_group      |        3 |            1 | zone1 | 172.20.249.52 |            0 | RANDOM            |
| obmysql     | bmsql_new_order  | tpccdb        | tpcc_group      |        3 |            2 | zone3 | 172.20.249.51 |            0 | RANDOM            |
| obmysql     | bmsql_oorder     | tpccdb        | tpcc_group      |        3 |            0 | zone2 | 172.20.249.49 |            0 | RANDOM            |
| obmysql     | bmsql_oorder     | tpccdb        | tpcc_group      |        3 |            1 | zone1 | 172.20.249.52 |            0 | RANDOM            |
| obmysql     | bmsql_oorder     | tpccdb        | tpcc_group      |        3 |            2 | zone3 | 172.20.249.51 |            0 | RANDOM            |
| obmysql     | bmsql_order_line | tpccdb        | tpcc_group      |        3 |            0 | zone2 | 172.20.249.49 |            0 | RANDOM            |
| obmysql     | bmsql_order_line | tpccdb        | tpcc_group      |        3 |            1 | zone1 | 172.20.249.52 |            0 | RANDOM            |
| obmysql     | bmsql_order_line | tpccdb        | tpcc_group      |        3 |            2 | zone3 | 172.20.249.51 |            0 | RANDOM            |
| obmysql     | bmsql_stock      | tpccdb        | tpcc_group      |        3 |            0 | zone2 | 172.20.249.49 |            0 | RANDOM            |
| obmysql     | bmsql_stock      | tpccdb        | tpcc_group      |        3 |            1 | zone1 | 172.20.249.52 |            0 | RANDOM            |
| obmysql     | bmsql_stock      | tpccdb        | tpcc_group      |        3 |            2 | zone3 | 172.20.249.51 |            0 | RANDOM            |
| obmysql     | bmsql_warehouse  | tpccdb        | tpcc_group      |        3 |            0 | zone2 | 172.20.249.49 |            0 | RANDOM            |
| obmysql     | bmsql_warehouse  | tpccdb        | tpcc_group      |        3 |            1 | zone1 | 172.20.249.52 |            0 | RANDOM            |
| obmysql     | bmsql_warehouse  | tpccdb        | tpcc_group      |        3 |            2 | zone3 | 172.20.249.51 |            0 | RANDOM            |
+-------------+------------------+---------------+-----------------+----------+--------------+-------+---------------+--------------+-------------------+
26 rows in set (0.095 sec)
```

`role` 是副本角色。1 表示 主副本， 2 表示 备副本。

下面 SQL 是 OBPROXY 查看一个具体的分区副本位置。视图 `__all_virtual_proxy_schema` 的访问要求非常严格，必须提供下面 4 个条件。通常不建议运维人员自己查询这个视图。

```sql
select table_name,partition_id, part_num, svr_ip,role, replica_num, table_type
from __all_virtual_proxy_schema
where tenant_name = 'obmysql' and database_name = 'tpccdb' and table_name = 'bmsql_oorder' and partition_id = 1 ;

+--------------+--------------+----------+---------------+------+-------------+------------+
| table_name   | partition_id | part_num | svr_ip        | role | replica_num | table_type |
+--------------+--------------+----------+---------------+------+-------------+------------+
| bmsql_oorder |            1 |        3 | 172.20.249.49 |    2 |           3 |          3 |
| bmsql_oorder |            1 |        3 | 172.20.249.51 |    2 |           3 |          3 |
| bmsql_oorder |            1 |        3 | 172.20.249.52 |    1 |           3 |          3 |
+--------------+--------------+----------+---------------+------+-------------+------------+
3 rows in set (0.028 sec)
```

OBPROXY 获取到每个分区的位置缓存在进程内部。如果后端 OBSERVER 节点宕机，发生部分分区主备故障切换，OBPROXY 不一定能立即感知到，而是在 SQL 发到老的主副本节点时报错后， OBPROXY 重新获取该分区新的主副本位置，然后更新自己的分区位置缓存。

## 调整 PRIMARY_ZONE 设置

PRIMARY_ZONE 就是设置主副本分布策略。通常可以在租户级别设置。MySQL 租户还可以在数据库级别再设置，数据库设置覆盖租户默认设置。此外，也可以在表分组或表级别设置，表分组和表的 PRIMARY_ZONE 设置会覆盖数据库或者租户的默认设置。

当调整主副本分布策略后，OceanBase 会很快发起在线主备切换。

+ 查看租户的 PRIMARY_ZONE 和 LOCALITY 设置。

 ```sql
 +-----------+-------------+-------------------+---------------------------------------------+
| tenant_id | tenant_name | primary_zone      | locality                                    |
+-----------+-------------+-------------------+---------------------------------------------+
|         1 | sys         | zone1;zone2,zone3 | FULL{1}@zone1, FULL{1}@zone2, FULL{1}@zone3 |
|      1002 | obmysql     | RANDOM            | FULL{1}@zone1, FULL{1}@zone2, FULL{1}@zone3 |
+-----------+-------------+-------------------+---------------------------------------------+
2 rows in set (0.011 sec)
 ```

 PRIMARY_ZONE 取值可以有很多组合：

+ `zone1` ，等同于 `zone1; zone2, zone3` 。即主副本优先分布在 `zone1` 的节点上。如果 `zone1` 的节点不可用，会选举 `zone2` 或 `zone3` 中的节点。
+ `zone1; zone2; zone3` 。即主副本优先分布在  `zone1`的节点上。如果 `zone1`的节点不可用，优先考虑 `zone2` 的节点，其次才是考虑 `zone3` 的节点。
+ RANDOM 。即主副本随机分布。赋值语句 `set primary_zone =RANDOM` , 不需要单引号。
+ DEFAULT。即清空 PRIMARY_ZONE 设置，遵守默认值，只能设置数据库和表分组、表的 PRIMARY_ZONE 为 `default` 。

 LOCALITY 设置是描述每个 ZONE 里的副本类型，默认情况下都是全功能副本（FULL）。其他副本类型还有：日志副本（LOGONLY）、只读副本（READONLY) 。

+ 查看数据库的 PRIMARY_ZONE 设置。

 可以通过查看数据库的定义获取。

 ```sql
 
MySQL [test]> alter database test primary_zone='zone2';
Query OK, 0 rows affected (0.310 sec)

MySQL [test]> show create database test;
+----------+-----------------------------------------------------------------------------------------------------------+
| Database | Create Database                                                                                           |
+----------+-----------------------------------------------------------------------------------------------------------+
| test     | CREATE DATABASE `test` DEFAULT CHARACTER SET = utf8mb4 REPLICA_NUM = 3 PRIMARY_ZONE = 'zone2;zone1,zone3' |
+----------+-----------------------------------------------------------------------------------------------------------+
1 row in set (0.016 sec)

```

+ 查看表分组和表的 PRIMARY_ZONE 设置

```sql
MySQL [test]> alter tablegroup tpcc_group primary_zone=RANDOM;
Query OK, 0 rows affected (0.427 sec)

MySQL [test]> show create tablegroup tpcc_group\G
*************************** 1. row ***************************
       Tablegroup: tpcc_group
Create Tablegroup: CREATE TABLEGROUP IF NOT EXISTS `tpcc_group`  PRIMARY_ZONE = RANDOM BINDING = FALSE
  partition by hash partitions 3

1 row in set (0.010 sec)

MySQL [test]> alter table t1 primary_zone='zone3;zone2;zone1';
Query OK, 0 rows affected (0.405 sec)

MySQL [test]> show create table t1\G
*************************** 1. row ***************************
       Table: t1
Create Table: CREATE TABLE `t1` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `c1` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) AUTO_INCREMENT = 1000001 DEFAULT CHARSET = utf8mb4 ROW_FORMAT = COMPACT COMPRESSION = 'zstd_1.3.8' REPLICA_NUM = 3 PRIMARY_ZONE = 'zone3;zone2;zone1' BLOCK_SIZE = 16384 USE_BLOOM_FILTER = FALSE TABLET_SIZE = 134217728 PCTFREE = 0
1 row in set (0.082 sec)

```

+ 查看分区主备副本切换事件

调整 PRIMARY_ZONE 可能引起分区副本主备角色切换，这个是在线切换，立即生效，对业务影响非常小。
可以通过下面 SQL 查看切换历史

```sql
select t.table_name, h.partition_idx, h.gmt_create, h.svr_ip, h.event, h.leader, h.info
from `__all_virtual_election_event_History` h join __all_virtual_table t  on (h.table_id=t.table_id)
where t.table_id >> 40 = 1002
order by h.gmt_create desc 
limit 10;

+------------------+---------------+----------------------------+---------------+-----------------+----------------------+--------------------+
| table_name       | partition_idx | gmt_create                 | svr_ip        | event           | leader               | info               |
+------------------+---------------+----------------------------+---------------+-----------------+----------------------+--------------------+
| bmsql_order_line |             1 | 2021-10-03 16:31:05.822946 | 172.20.249.52 | leader takevoer | "172.20.249.52:2882" | change leader succ |
| bmsql_order_line |             1 | 2021-10-03 16:31:05.822150 | 172.20.249.49 | leader takevoer | "172.20.249.52:2882" | change leader succ |
| bmsql_order_line |             1 | 2021-10-03 16:31:05.821211 | 172.20.249.51 | leader revoke   | "172.20.249.51:2882" | old leader revoke  |
| bmsql_order_line |             1 | 2021-10-03 16:31:05.821211 | 172.20.249.51 | leader takevoer | "172.20.249.52:2882" | renew lease succ   |
| nation           |             0 | 2021-10-03 16:31:05.817790 | 172.20.249.51 | leader takevoer | "172.20.249.51:2882" | change leader succ |
| sbtest7          |             0 | 2021-10-03 16:31:05.817790 | 172.20.249.51 | leader takevoer | "172.20.249.51:2882" | change leader succ |
| sbtest1          |             0 | 2021-10-03 16:31:05.817790 | 172.20.249.51 | leader takevoer | "172.20.249.51:2882" | change leader succ |
| supplier         |             3 | 2021-10-03 16:31:05.817790 | 172.20.249.51 | leader takevoer | "172.20.249.51:2882" | change leader succ |
| sbtest8          |             0 | 2021-10-03 16:31:05.817790 | 172.20.249.51 | leader takevoer | "172.20.249.51:2882" | change leader succ |
| customer         |             0 | 2021-10-03 16:31:05.817790 | 172.20.249.51 | leader takevoer | "172.20.249.49:2882" | change leader succ |
+------------------+---------------+----------------------------+---------------+-----------------+----------------------+--------------------+
10 rows in set (0.107 sec)

```

## LDC 设置

OceanBase 数据库作为典型的高可用分布式关系型数据库，使用 Paxos 协议进行日志同步，天然支持多地多中心的部署方式以提供高可靠的容灾保证。但当真正多地多中心部署时，任何数据库都会面临异地路由延迟问题。

逻辑数据中心（Logical Data Center，LDC）路由正是为了解决这一问题而设计的，通过给 OceanBase 集群的每个 ZONE 设置 REGION 属性和 IDC 属性，并给 OBProxy 指定 IDC 名称配置项。当 OBPROXY 考虑随机发送时，会优先考虑同一个 IDC 或者同一个 REGION 的可用节点。

配置 LDC 方法：

+ OceanBase 集群节点添加 REGION 和 IDC 属性。

```sql
ALTER SYSTEM MODIFY zone zone1 SET region = SHANGHAI; 
ALTER SYSTEM MODIFY zone zone1 SET idc = SH_IDC1;

ALTER SYSTEM MODIFY zone zone2 SET region = SHANGHAI; 
ALTER SYSTEM MODIFY zone zone2 SET idc = SH_IDC2;

ALTER SYSTEM MODIFY zone zone3 SET region = HANGZHOU; 
ALTER SYSTEM MODIFY zone zone3 SET idc = SH_IDC3;
```

一个集群可以有若干 REGION，一个 REGION 有若干个 ZONE ，每个 ZONE 对应一个 IDC（可以重复，如同机房三副本）。REGION 和 IDC 并不是随意设置就可以。需要保证租户的 PRIMARY_ZONE 里至少有两个 FULL 副本。

```sql
MySQL [oceanbase]> select * from __all_zone where name in ('region','idc');
+----------------------------+----------------------------+-------+--------+-------+---------+
| gmt_create                 | gmt_modified               | zone  | name   | value | info    |
+----------------------------+----------------------------+-------+--------+-------+---------+
| 2021-09-25 08:19:05.067944 | 2021-10-04 14:28:03.262961 | zone1 | idc    |     0 | idc1    |
| 2021-09-25 08:19:05.067944 | 2021-10-04 14:29:17.004134 | zone1 | region |     0 | region1 |
| 2021-09-25 08:19:05.068993 | 2021-10-04 14:28:12.236866 | zone2 | idc    |     0 | idc2    |
| 2021-09-25 08:19:05.068993 | 2021-10-04 14:29:23.306688 | zone2 | region |     0 | region2 |
| 2021-09-25 08:19:05.070055 | 2021-10-04 14:28:19.560596 | zone3 | idc    |     0 | idc3    |
| 2021-09-25 08:19:05.070055 | 2021-10-04 14:29:30.604319 | zone3 | region |     0 | region3 |
+----------------------------+----------------------------+-------+--------+-------+---------+
6 rows in set (0.012 sec)
```

像上面这个设置就有问题，租户的 PRIMARY_ZONE 设置为任意一个 ZONE 都会报错。

```sql
MySQL [oceanbase]> alter tenant sys primary_zone='zone1';
ERROR 4179 (HY000): primary zone F type replica not enough in its region not allowed

MySQL [oceanbase]> alter tenant sys primary_zone='zone1';
Query OK, 0 rows affected (0.095 sec)

MySQL [oceanbase]> select tenant_name,primary_zone,locality from __all_tenant;
+-------------+--------------+---------------------------------------------+
| tenant_name | primary_zone | locality                                    |
+-------------+--------------+---------------------------------------------+
| sys         | zone1;zone2  | FULL{1}@zone1, FULL{1}@zone2, FULL{1}@zone3 |
| obmysql     | RANDOM       | FULL{1}@zone1, FULL{1}@zone2, FULL{1}@zone3 |
+-------------+--------------+---------------------------------------------+
2 rows in set (0.003 sec)
```

此时调整了 ZONE2 的 REGION 属性跟 ZONE1 的 REGION 属性一致后，就可以设置 PRIMARY_ZONE 为 `zone1` 或 `zone2` 。并且 `zone3` 不再出现在候选里了，因为 `zone3` 的 REGION `region3` 里只有一个 FULL 副本，不具备设置为 PRIMARY_ZONE 的资格。

开启 LDC 后，PRIMARY_ZONE 在设置为 RANDOM 还可能会碰到一个限制。

```sql
MySQL [oceanbase]> alter tenant obmysql primary_zone=RANDOM;
ERROR 1235 (0A000): tenant primary zone span regions when GTS is on not supported

```

登录业务租户查看

```sql
MySQL [test]> show global variables like 'ob_timestamp_service';
+----------------------+-------+
| Variable_name        | Value |
+----------------------+-------+
| ob_timestamp_service | GTS   |
+----------------------+-------+
1 row in set (0.008 sec)

MySQL [test]> set global ob_timestamp_service = LTS;
Query OK, 0 rows affected (0.120 sec)
```

再登录 SYS 租户修改。

```sql
MySQL [oceanbase]> alter tenant obmysql primary_zone=RANDOM;
Query OK, 0 rows affected (0.078 sec)

MySQL [oceanbase]> select tenant_name,primary_zone,locality from __all_tenant;
+-------------+-------------------+---------------------------------------------+
| tenant_name | primary_zone      | locality                                    |
+-------------+-------------------+---------------------------------------------+
| sys         | zone1;zone2,zone3 | FULL{1}@zone1, FULL{1}@zone2, FULL{1}@zone3 |
| obmysql     | RANDOM            | FULL{1}@zone1, FULL{1}@zone2, FULL{1}@zone3 |
+-------------+-------------------+---------------------------------------------+
2 rows in set (0.014 sec)
```

这个时候虽然设置成功了，不过由于时间服务改用了 LTS ，那么就不支持跨节点的一致性读快照了。就会遇到下面这个问题。

```sql
MySQL [tpccdb]> select /*+ read_consistency(strong) query_timeout(10000000) */ count(*) from bmsql_warehouse;
ERROR 1235 (0A000): strong consistency across distributed node not supported
MySQL [tpccdb]>
MySQL [tpccdb]> select /*+ read_consistency(weak) */ count(*) from bmsql_warehouse;
+----------+
| count(*) |
+----------+
|       10 |
+----------+
1 row in set (0.269 sec)

```

LTS 服务下不支持跨节点的分布式查询，这个是业务不能接受的。所以反过来，当 OceanBase 集群有多个 REGION 的时候，就不能设置 PRIMARY_ZONE 为 RANDOM 。

+ OBPROXY 的 IDC 属性

 OBPROXY 启动时可以指定参数 `proxy_idc_name` 为具体的 IDC ，也可以启动后修改参数值。

 ```sql
 MySQL [(none)]> show proxyconfig like 'proxy_idc_name';
+----------------+-------+-----------------------------------------------------------------------------------------------------------------------------------+-------------+---------------+
| name           | value | info                                                                                                                              | need_reboot | visible_level |
+----------------+-------+-----------------------------------------------------------------------------------------------------------------------------------+-------------+---------------+
| proxy_idc_name |       | idc name for proxy ldc route. If is empty or invalid, treat as do not use ldc. User session vars 'proxy_session_ldc' can cover it | false       | SYS           |
+----------------+-------+-----------------------------------------------------------------------------------------------------------------------------------+-------------+---------------+
1 row in set (0.008 sec)

MySQL [(none)]> alter proxyconfig set proxy_idc_name = 'idc1';
Query OK, 0 rows affected (0.044 sec)

MySQL [(none)]> show proxyconfig like 'proxy_idc_name';
+----------------+-------+-----------------------------------------------------------------------------------------------------------------------------------+-------------+---------------+
| name           | value | info                                                                                                                              | need_reboot | visible_level |
+----------------+-------+-----------------------------------------------------------------------------------------------------------------------------------+-------------+---------------+
| proxy_idc_name | idc1  | idc name for proxy ldc route. If is empty or invalid, treat as do not use ldc. User session vars 'proxy_session_ldc' can cover it | false       | SYS           |
+----------------+-------+-----------------------------------------------------------------------------------------------------------------------------------+-------------+---------------+
1 row in set (0.051 sec)

MySQL [(none)]> show proxyinfo idc;
+-----------------+--------------+----------------+----------------+--------------+--------------------------+--------------+
| global_idc_name | cluster_name | match_type     | regions_name   | same_idc     | same_region              | other_region |
+-----------------+--------------+----------------+----------------+--------------+--------------------------+--------------+
| idc1            | obce-3zones  | MATCHED_BY_IDC | [[0]"region1"] | [[0]"zone1"] | [[0]"zone2", [1]"zone3"] | []           |
+-----------------+--------------+----------------+----------------+--------------+--------------------------+--------------+
1 row in set (0.010 sec)
 ```

## OBPROXY 路由策略简介

OBPROXY 的路由策略非常丰富，本节只是做一个大概的介绍。对于 OBPROXY 的每条路由策略，只需要知道是什么就够了。

### 弱一致性读路由策略

 OceanBase 里默认读是强一致性读，即写后读立即可见（`READ AFTER WRITER`)。
 强一致性读语句，OBPROXY 会优先路由到访问表的分区的主副本节点上，这点没有疑义。跟强一致性读对立的就是弱一致性读。弱一致性读不要求写后读立即可见，
 弱一致性读可以路由到分区的主副本和备副本节点。通常有三副本所在节点可以选。但是开启弱一致性读后，如果 OBSERVER 和 OBPROXY 都开启了 LDC 特性，那么弱一致性读语句的路由策略就是优先路由到同一个机房或者同一个 REGION 的状态不是合并中（`merging`）的节点，其次是合并中的节点，最后是其他 REGION的不在合并的或在合并的节点。

也就是说 OBPROXY 会尽力避开合并中的节点。不过当 OceanBase 集群关闭了轮转合并（参数 `enable_merge_by_turn` 设置为 `false` ），合并（`major freeze`）的时候是所有节点都开始合并了，那么 OBPROXY 也就避不开了合并中的节点。

还有一些 SQL 不是访问数据，而是查看或者设置变量值等。如：

```sql
set @@autocommit=off
show variables like 'autocommit';
```

这类 SQL 的路由策略就是随机路由了。如果 OBSERVER 和 OBPROXY 开启了 LDC 设置，也会遵守上面特别的路由策略。

 弱一致性读通常用在读写分离场景。不过在租户 PRIMARY_ZONE 为 RANDOM 的场景下，租户的所有分区的主副本也是分散在所有 ZONE 下，这时候弱一致性读备副本的意义也不是很大。
但是，如果使用了 只读副本。只读副设置为独立的 IDC，然后单独的 OBPROXY 设置为同一个 IDC，则这个 OBPROXY 可以用于只读副本的路由。

### 强一致性读路由策略

路由策略很多，这里由简单到复杂，列举一些。

+ 强一致性读路由策略就是将 SQL 路由到访问的表的分区的主副本所在节点。
这一条理解起来比较简单，实际 SQL 情形很复杂。
+ 如果 SQL 访问了两个表，会依据第一个表及其条件判断出该分区主副本节点。如果得不到，就随机发。所以 SQL 里多表连接时，表的前后顺序对路由策略是有影响的，间接对性能有影响。
+ 补充说明，如果要判断的表是分区表，会看条件是否是分区键等值条件。如果不是，则不能确定是哪个分区，就随机发到该表的所有分区所在的节点任意一个。
+ 如果开启事务了，则事务里开启事务的 SQL 的路由节点会作为事务后面其他 SQL 路由的目标节点，直到事务结束（提交或者回滚）为止。

当SQL 被 OBPROXY 路由到一个节点上时，如果要访问的数据分区的主副本恰好在那个节点上，SQL就在该节点执行，这个SQL的执行类型是本地SQL（`plan_type` 为1）。
如果要访问的数据分区的主副本不在这个节点上，SQL 会被 OBSERVER 再次转发。这个 SQL 的执行类型是 远程SQL（`plan_type` 为2）。
如果 SQL 执行计划要访问的数据分区是跨越多个节点，则这个SQL 的执行类型是 分布式SQL（`plan_type` 为3）。

+ 如果事务中有复制表的读 SQL，只要 SQL 被路由到的节点上有该复制表的备副本，则该 SQL 可以读取本地备副本。因为复制表的所有备副本跟主副本是强一致性。这个SQL 的执行类型是本地 SQL。

实际 SQL 类型很复杂，OBPROXY 的路由策略也变得很复杂。有时候会出现路由不准的情形。如果不符合设计预期就是 BUG，但很可能也是设计如此（`BY DESIGN`)。毕竟当前版本的 OBPROXY 只能做简单的 SQL 解析，不像 OBSERVER 那样做完整的执行计划解析。

当业务 SQL 很多很复杂时，远程 SQL 和分布式 SQL 会无法避免。主要观察其比例。比例很高的话，整体上业务 SQL 性能都不会很好。这时候优化策略就是尽可能的减少远程 SQL 和分布式 SQL 了。 常用的手段就是表分组、复制表和 PRIMARY_ZONE 设置了。
