diff --git a/docs/user_manual/operation_and_maintenance/operations_and_maintenance/commonly_used_sql/_index.md b/docs/user_manual/operation_and_maintenance/operations_and_maintenance/commonly_used_sql/_index.md deleted file mode 100644 index b4649f488..000000000 --- a/docs/user_manual/operation_and_maintenance/operations_and_maintenance/commonly_used_sql/_index.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: 运维常用 SQL -weight: 2 ---- \ No newline at end of file diff --git a/docs/user_manual/operation_and_maintenance/operations_and_maintenance/optimizer_statistics/01_operations_and_maintenance.md b/docs/user_manual/operation_and_maintenance/operations_and_maintenance/optimizer_statistics/01_operations_and_maintenance.md new file mode 100644 index 000000000..e14a86980 --- /dev/null +++ b/docs/user_manual/operation_and_maintenance/operations_and_maintenance/optimizer_statistics/01_operations_and_maintenance.md @@ -0,0 +1,380 @@ +--- +title: 统计信息运维手册 +weight: 1 +--- + +> **本篇统计信息运维手册,对应的 OceanBase 数据内核版本是 4.2.0 及以上的社区版本。统计信息收集默认会刷新该表的 plan cache,业务高峰期非统计信息问题,收集统计信息需要慎重!** + +## 统计信息基础知识 / 操作 + +在阅读本篇内容之前,建议先学习《OceanBase DBA 进阶教程》中的 [“统计信息”](https://www.oceanbase.com/docs/community-tutorials-cn-1000000001390071#0-title-%E7%BB%9F%E8%AE%A1%E4%BF%A1%E6%81%AF) 部分。 + +> 如果已经对 OceanBase 中的统计信息有了初步了解,可以跳过基础知识部分,直接阅读下面的内容。 + + + + +## 自动统计信息收集问题排查 {#自动统计信息收集问题排查} +### 自动统计信息收集工作原理 +目前自动统计信息收集任务是基于 DBMS_SCHEDULER 系统包实现,以周为单位,定义了如下 7 个窗口的定时执行任务: + +| **维护窗口名字** | **开始时间 / 频率** | **最大收集时长** | +| :---: | :---: | :---: | +| **MONDAY_WINDOW** | 22:00/per week | 4 hours | +| **TUESDAY_WINDOW** | 22:00/per week | 4 hours | +| **WEDNESDAY_WINDOW** | 22:00/per week | 4 hours | +| **THURSDAY_WINDOW** | 22:00/per week | 4 hours | +| **FRIDAY_WINDOW** | 22:00/per week | 4 hours | +| **SATURDAY_WINDOW** | 6:00/per week | 20 hours | +| **SUNDAY_WINDOW** | 6:00/per week | 20 hours | + +自动收集收集的是统计信息缺失或者统计信息过期的表,按照增量收集的方式进行收集。也就是只收集数据变化的分区,而不用重新收集整个表的统计信息。过期标准是看针对每个分区来说增量的 DML info 变化是否满足过期的阈值(默认:10%,即单个分区距离上一次收集期间增删改的总量不超过当前表数据量的 10%)。 + + +### 自动统计信息收集是否正常排查 {#自动统计信息收集是否正常排查} +请按照如下步骤进行排查: + ++ **步骤一:按照租户类别使用如下 SQL 查询进行检查,主要检查最近一天内的所有租户自动收集是否有正常调度。如果调度正常,跳转步骤二,否则请先参考本文中的 “[自动统计收集任务调度问题排查](#自动统计收集任务调度问题排查)” 部分。** + +```sql + +-- sys租户,查询非空则说明有租户调度异常(推荐) + +SELECT tenant_id AS failed_scheduler_tenant_id +FROM oceanbase.__all_tenant t +WHERE NOT EXISTS(SELECT 1 + FROM oceanbase.__all_virtual_task_opt_stat_gather_history h + WHERE TYPE = 1 + AND start_time > date_sub(now(), interval 1 day) + AND h.tenant_id = t.tenant_id); + + +-- MySQL 模式的普通租户,查询为空则说明有租户调度异常 + +SELECT * +FROM oceanbase.dba_ob_task_opt_stat_gather_history +WHERE start_time > date_sub(now(), interval 1 day) + AND TYPE = 'AUTO GATHER'; +``` + +**步骤二:按照租户类别使用如下 SQL 查询进行检查,获取过去一天的自动收集是表收集失败列表。如果为空则说明自动收集正常,如果非空,则跳转步骤三进一步检查失败原因。** + +```sql + +-- sys 租户,获取的所有租户信息。也可以指定租户查询(推荐) + +SELECT t_opt.tenant_id, + t_opt.task_id, + task_opt.start_time AS task_start_time, + task_opt.end_time AS task_end_time, + d.database_name, + t.table_name, + t_opt.table_id, + t_opt.ret_code, + t_opt.start_time, + t_opt.end_time, + t_opt.memory_used, + t_opt.stat_refresh_failed_list, + t_opt.properties +FROM ( + SELECT tenant_id, + task_id, + start_time, + end_time, + table_count + FROM oceanbase.__all_virtual_task_opt_stat_gather_history + WHERE type = 1 +-- AND tenant_id = {tenant_id} -- 这里可以指定目标租户的 tenant_id + AND start_time > date_sub(Now(), interval 1 day)) task_opt +JOIN oceanbase.__all_virtual_table_opt_stat_gather_history t_opt +JOIN oceanbase.__all_virtual_table t +JOIN oceanbase.__all_virtual_database d +WHERE t_opt.ret_code != 0 +AND task_opt.task_id = t_opt.task_id +AND task_opt.tenant_id = t_opt.tenant_id +AND t_opt.tenant_id = t.tenant_id +AND t_opt.table_id = t.table_id +AND t.tenant_id = d.tenant_id +AND t.database_id = d.database_id +AND t_opt.table_id > 200000; + + +-- MySQL 模式的普通租户 + +SELECT task_opt.task_id, + task_opt.start_time AS task_start_time, + task_opt.end_time AS task_end_time, + t_opt.owner, + t_opt.table_name, + t_opt.start_time, + t_opt.end_time, + t_opt.memory_used, + t_opt.stat_refresh_failed_list, + t_opt.properties +FROM (SELECT task_id, + start_time, + end_time + FROM oceanbase.dba_ob_task_opt_stat_gather_history + WHERE start_time > Date_sub(Now(), interval 1 day) + AND TYPE = 'AUTO GATHER') task_opt + join oceanbase.dba_ob_table_opt_stat_gather_history t_opt + ON task_opt.task_id = t_opt.task_id + AND t_opt.status != 'SUCCESS' + AND owner != 'oceanbase'; +``` + ++ **步骤三,针对上述收集失败的表,按照如下场景选择应对方式:** + +1. **(最常见场景) ** 收集失败的表是一个数据量大表(行数超过上亿),长时间没收集成功,出现收集超时报错 ret=-4012,请按照如下方式进行解决:[“自动收集卡在超大表运维手段”](#自动收集卡在超大表运维手段)。 + +2. 租户中的表太多,大部分表都需要重新收集统计信息,但是收集窗口时间有限,导致未收集完成。该场景下需要考虑重新对该租户在 **业务低峰期** 的时候手动收集一次,收集策略可参考:[《手动统计信息收集命令使用手册》](https://oceanbase.github.io/docs/user_manual/operation_and_maintenance/operations_and_maintenance/optimizer_statistics/command)。 + +3. 非超时报错,其他错误码,请先在 **业务低峰期 ** 对该表重新手动收集一次统计信息(详见:《手动统计信息收集命令使用手册》)。后续继续观察,同时将该报错问题反馈给 OceanBase 社区论坛的官方值班同学。 + +## 自动统计收集任务调度问题排查 {#自动统计收集任务调度问题排查} +可以根据当前的租户情况,使用如下查询检查自动收集任务是否调度正常: + ++ **sys 租户(需要指定目标租户的 tenant_id)**: + +```sql +-- 查询目标租户所有窗口是否正常在调度执行,主要检查对应的调度是否正常,时间有无错乱等: + +SELECT tenant_id, + job_name, + what, + start_date, + this_date, + last_date, + next_date, + enabled +FROM oceanbase.__all_virtual_tenant_scheduler_job +WHERE tenant_id = {tenant_id} + AND job_name IN ( 'MONDAY_WINDOW', 'TUESDAY_WINDOW', 'WEDNESDAY_WINDOW', + 'THURSDAY_WINDOW', + 'FRIDAY_WINDOW', 'SATURDAY_WINDOW', 'SUNDAY_WINDOW' ) + AND job != 0; + + +-- 查询目标租户上一次调度执行情况, 主要检查 code 字段是否为 0 (错误码为 0 表示 success): + +SELECT * +FROM OCEANBASE.__ALL_VIRTUAL_TENANT_SCHEDULER_JOB_RUN_DETAIL +WHERE tenant_id = {tenant_id} +ORDER BY time; +``` + ++ **MySQL 模式的普通租户:** + +```sql +-- 查询当前租户所有窗口是否正常在调度执行: + +SELECT job_name, + job_action, + start_date, + last_start_date, + next_run_date, + enabled +FROM oceanbase.dba_scheduler_jobs +WHERE job_name IN ( 'MONDAY_WINDOW', 'TUESDAY_WINDOW', 'WEDNESDAY_WINDOW', + 'THURSDAY_WINDOW', + 'FRIDAY_WINDOW', 'SATURDAY_WINDOW', 'SUNDAY_WINDOW' ); + +-- 查询当前租户上一次调度执行情况(code 字段是否为 0): + +SELECT * +FROM OCEANBASE.__ALL_TENANT_SCHEDULER_JOB_RUN_DETAIL +ORDER BY time; +``` + +如果上述查询结果调度异常,可以将该报错问题反馈给 OceanBase 社区论坛的官方值班同学。 + +## 自动收集卡在超大表运维手段 {#自动收集卡在超大表运维手段} +当前业务场景中有比较多的租户,因为有一个大表收集缓慢,导致自动收集任务失败,是否是这个原因导致可通过 [“自动统计信息收集问题排查”](#自动统计信息收集问题排查) 来确认,当确认是某个大表所导致的问题,可以使用如下策略进行运维: + ++ **步骤一:使用如下 SQL 检查当前大表过去一段时间的收集情况,观察是否都是一个收集耗时长的过程,如果查询 sys 租户的 ret_code 字段非 0 表示收集失败,查询业务租户视图的 status 字段非 'SUCCESS' 或者 NULL 表示收集失败** + +```sql +-- sys 租户 + +SELECT * +FROM oceanbase.__all_virtual_table_opt_stat_gather_history +WHERE table_id = {table_id} +ORDER BY start_time; + +-- MySQL 模式的普通租户 + +SELECT * +FROM oceanbase.dba_ob_table_opt_stat_gather_history +WHERE table_name = '{table_name}' +ORDER BY start_time; +``` + ++ **步骤二:可以考虑大表的收集策略,参考这个标准:**[“大表统计信息收集策略调整“](#大表统计信息收集策略调整) + ++ **步骤三:调整完收集策略之后,需要明确是否有必要重新手动收集一次该表的统计信息。如果该表统计信息过期很严重,相关查询的计划生成都有问题,则可以考虑看系统资源是否充足,加大并行收集统计信息;其他情况如果想确认下步骤二设置的策略能否有效,推算自动统计信息收集能发成功,则可以使用如下查询** + +```plsql +-- MySQL 模式的普通租户 +-- 为了稳定,本次收集统计信息不刷新 plan cache 重新生成计划 +call dbms_stats.gather_table_stats('database_name','table_name', no_invalidate=>true); +``` + ++ **步骤四:如果当前租户已经长时间出现大表收集卡住的问题,可以通过 [“快速获取当前租户中统计信息过期或者统计信息缺失的表”](#快速获取当前租户中统计信息过期或者统计信息缺失的表) 中的方式查询当前租户中是否已经存在大量表统计信息缺失或者过期的问题,如果存在则需要在业务低峰期手动重新收集相关表的统计信息(参考《手动统计信息收集命令使用手册》 )。** + ++ **步骤五:以上步骤完成之后,最后一步可以考虑调整自动统计信息收集发起任务的时间,尽量错开业务的高峰期,放到每日合并之后进行。调整自动收集任务时间参考:[调整自动统计信息收集的调度时间](#调整自动统计信息收集的调度时间)** + +如果上述步骤有不清楚的地方,或者操作过程有异常的问题,请联系 OceanBase 社区论坛值班同学协助排查。 + +## 统计信息常用运维手段 +### 禁止/启用自动统计信息收集 +通过如下方式进行禁止或启用自动统计信息收集,同时需要注意的是启用自动统计信息收集之后需要重新调整。 + +```sql +-- MySQL 租户: +-- 禁止 +call dbms_scheduler.disable('MONDAY_WINDOW'); +call dbms_scheduler.disable('TUESDAY_WINDOW'); +call dbms_scheduler.disable('WEDNESDAY_WINDOW'); +call dbms_scheduler.disable('THURSDAY_WINDOW'); +call dbms_scheduler.disable('FRIDAY_WINDOW'); +call dbms_scheduler.disable('SATURDAY_WINDOW'); +call dbms_scheduler.disable('SUNDAY_WINDOW'); + +-- 启用 +call dbms_scheduler.enable('MONDAY_WINDOW'); +call dbms_scheduler.enable('TUESDAY_WINDOW'); +call dbms_scheduler.enable('WEDNESDAY_WINDOW'); +call dbms_scheduler.enable('THURSDAY_WINDOW'); +call dbms_scheduler.enable('FRIDAY_WINDOW'); +call dbms_scheduler.enable('SATURDAY_WINDOW'); +call dbms_scheduler.enable('SUNDAY_WINDOW'); +``` + +### 快速获取当前租户中统计信息过期或者统计信息缺失的表 {#快速获取当前租户中统计信息过期或者统计信息缺失的表} +通过如下查询,可以在业务租户中查询统计信息缺失或者过期的表,并且按照数据量排序: + +```sql +-- MySQL 租户 + +SELECT v2.database_name, + v2.table_name, + Sum(inserts - deletes) row_cnt +FROM oceanbase.dba_tab_modifications v1, + (SELECT DISTINCT database_name AS DATABASE_NAME, + table_name AS table_name + FROM oceanbase.dba_ob_table_stat_stale_info + WHERE is_stale = 'YES' + AND database_name != 'oceanbase') v2 +WHERE v1.table_name = v2.table_name +GROUP BY v2.database_name, + v2.table_name +ORDER BY row_cnt; +``` + +### 大表统计信息收集策略调整 {#大表统计信息收集策略调整} +针对大表的统计信息收集,其收集耗时主要在三个地方: + +1. **表数据量大,收集需要全表扫,耗时高;** + +2. **直方图收集涉及复杂的计算,带来额外成本的耗时;** + +3. **大分区表默认收集二级分区、一级分区、全表的统计信息和直方图,3 * (cost (全表扫) + cost (直方图))代价(内核422及其以上的版本已优化,421版本暂未解决)** + +因此根据上述耗时点,可以根据表的实际情况及相关查询情况进行优化,给出如下建议: + ++ 设置合适的默认收集并行度,需要注意的是设置并行度之后,需要调整相关的自动收集任务在业务低峰期进行([“调整自动统计信息收集的调度时间”](#调整自动统计信息收集的调度时间)),避免影响业务,**建议并行度控制 8 个以内。** 可使用如下方式设置: + +```sql +-- MySQL 租户: +call dbms_stats.set_table_prefs('database_name', 'table_name', 'degree', '8'); +``` + ++ 设置默认列的直方图收集方式,考虑给数据分布均匀的列设置不收集直方图: + +```sql +-- MySQL 租户 + +-- 1.如果该表所有列的数据分布都是均匀的,可以使用如下方式设置所有列都不收集直方图: + +call dbms_stats.set_table_prefs('database_name', 'table_name', 'method_opt', 'for all columns size 1'); + +-- 2.如果该表仅极少数列数据分布不均匀,需要收集直方图,其他列都不需要,则可以使用如下方式设置(c1,c2收集直方图,c3,c4,c5不收集直方图) + +call dbms_stats.set_table_prefs('database_name', 'table_name', 'method_opt', 'for columns c1 size 254, c2 size 254, c3 size 1, c4 size 1, c5 size 1'); +``` + ++ 设置默认分区表的收集粒度,针对一些分区表,形如 hash 分区/ key 分区之类的,可以考虑只收集全局的统计信息,或者也可以设置分区推导全局的收集方式: + +```sql +-- MySQL租户 + +-- 1.设置只收集全局的统计信息 +call dbms_stats.set_table_prefs('database_name', 'table_name', 'granularity', 'GLOBAL'); + +-- 2.设置分区推导全局的收集方式 +call dbms_stats.set_table_prefs('database_name', 'table_name', 'granularity', 'APPROX_GLOBAL AND PARTITION'); +``` + ++ **慎用设置大表采样的方式收集统计信息,设置大表采样收集时,直方图的样本数量也会变得很大,存在适得其反的效果,设置采样的方式收集仅仅适合只收集基础统计信息,不收集直方图的场景** + +```sql +-- MySQL 租户,删除 granularity + +-- 1.设置所有列都不收集直方图: +call dbms_stats.set_table_prefs('database_name', 'table_name', 'method_opt', 'for all columns size 1'); + +-- 2.设置采样比例为 10% +call dbms_stats.set_table_prefs('database_name', 'table_name', 'estimate_percent', '10'); +``` + + 除此之外,如果需要清空 / 删除已设置的默认收集策略,只需要指定清除的属性即可。可以使用如下方式: + +```sql +-- MySQL 租户,删除 granularity + +call dbms_stats.delete_table_prefs('database_name', 'table_name', 'granularity'); +``` + + 如果设置好了相关收集策略,需要查询是否设置成功,可以使用如下方式查询: + +```sql +-- MySQL 租户,如获取指定的并行度 degree + +select dbms_stats.get_prefs('degree', 'database_name','table_name') from dual; +``` +除了上述方式以外,也可以考虑能否手动收集完大表统计信息之后,锁定相关的统计信息,具体场景及使用方式参考:[如何锁定统计信息,避免统计信息更新](#如何锁定统计信息,避免统计信息更新) + +### 如何锁定统计信息,避免统计信息更新 {#如何锁定统计信息,避免统计信息更新} +针对一些整体数据分布变化不太大,想要维持相关表的查询计划稳定,可以使用如下的方式考虑锁定 / 解锁表的统计信息: + +```sql +-- MySQL 租户, 锁定表的统计信息 +call dbms_stats.lock_table_stats('database_name', 'table_name'); + +-- MySQL 租户, 解锁表的统计信息 +call dbms_stats.unlock_table_stats('database_name', 'table_name'); +``` + +**需要注意的是当表的统计信息锁定之后,自动收集将不会更新,适用于一些对数据变化不太大、数据值不敏感的场景。** 如果需要重新收集锁定的统计信息,需要先将其解锁。 + +### 统计信息收集慢的策略调整 +如果在表的统计信息收集过程中,感觉表收集很慢,可以按照如下策略调整统计信息收集策略: + +1. **优先选择关闭直方图的收集**(method_opt => 'for all columns size 1'),因为现阶段收集直方图是最费时的操作,同时很多场景目前也还用不上直方图。可以通过如下命令直接设置表不收集直方图: + +```sql +call dbms_stats.set_table_prefs('database_name', + 'table_name', + 'method_opt', + 'for all columns size 1'); +``` + +2. **增加并行度(degree=>xx)**,在业务低峰期可以考虑适当增加并行度,加速统计信息的收集。 +3. 可以使用PARTITION的方式收集统计信息 + +同时,**不建议直接调整 estimate_percent 这个选项**。默认情况下,直方图收集是采样少量数据后计算的;如果调整了这个配置项,直方图会直接根据这里指定的比例进行采样收集。这反而会大幅度拖慢直方图的收集,同时基础统计信息收集也没有那么准确。 + +## 参考资料 +- [OceanBase 官网文档:统计信息和估行机制](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000001576721) + +- [OceanBase 社区论坛](https://ask.oceanbase.com/) diff --git a/docs/user_manual/operation_and_maintenance/operations_and_maintenance/optimizer_statistics/02_best_practices.md b/docs/user_manual/operation_and_maintenance/operations_and_maintenance/optimizer_statistics/02_best_practices.md new file mode 100644 index 000000000..5e2e0670d --- /dev/null +++ b/docs/user_manual/operation_and_maintenance/operations_and_maintenance/optimizer_statistics/02_best_practices.md @@ -0,0 +1,156 @@ +--- +title: 统计信息收集最佳实践 +weight: 2 +--- + +> 说明:目前 DBA 进阶教程的内容暂时对应的是 OceanBase 4.x 社区版本,本小节的架构部分不涉及商业版中 Oracle 模式租户下的内容。社区版和商业版的能力区别详见:[官网链接](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000001428510)。 + +## 为何要收集统计信息 +优化器生成和选择最优的执行计划,往往是基于代价来评估的,因此一个执行计划的代价评估的是否准确,对于优化器的计划生成至关重要。 + +在代价计算的过程中,优化器会根据代价模型和每个算子估算出的行数评估以及各执行计划的代价。在这之中,统计信息扮演了重要的角色。统计信息是否准确直接影响了算子的行数估算,进一步会影响到计划的代价计算和计划的选择。 + +因此保持统计信息的准确性对于生成好的执行计划至关重要。 + +## 统计信息的默认收集策略 + +统计信息会在每个工作日(周一至周五)22:00 开始收集,最长收集 4 小时;或在每个非工作日(每周六周日)6:00 开始收集,最长收集 20 小时。这些统计信息收集的时间段我们称之为统计信息维护窗口。 + +在每个统计信息维护窗口内,优化器会为每个统计信息过期的表/分区重新收集统计信息。默认情况下统计信息过期的判定是一个表/分区上没有收集过统计信息,或表/分区上增删改的数据量超过了上次收集统计信息时行数的 **10%**。收集统计信息时,默认的收集策略如下: + +| **配置名称** | **含义** | **默认策略** | +| --- | --- | --- | +| degree | 并行度 | 1 并行度,单线程扫描。 | +| method_opt | 列级统计信息的收集策略 | 收集所有列的统计信息,并收集在where条件中出现过且存在数据倾斜列的直方图 | +| granularity | 收集颗粒 | 收集分区级的统计信息,并根据结果推导全局级的统计信息。非分区表的情况下直接收集全局级的统计信息。 | +| estimate_percent | 采样比例 | 不采样, 通过全表扫描手机统计信息。 | +| block_sample | 是否采样块采样 | 不使用块采样,使用行采样。 | + + +## 统计信息收集策略配置 +采用默认统计信息收集策略对与大多数的表都是适用的,但是在一些特定的场景下不一定能满足业务的场景,需要用户根据业务特点进行适当的配置。常见的场景及配置策略如下: + +### 业务高峰与统计信息维护窗口重叠 + + + +**OceanBase 统计信息维护窗口的默认设置参考了 Oracle 的设置。但是对于很多国内业务来说,工作日的 22:00 依然是业务高峰时期,此时收集统计信息可能会出现统计信息收集的 SQL 与业务 SQL 抢占资源,影响业务 SQL 的运行。针对这个场景,可以调整统计信息维护窗口的开始时间,使其与业务高峰错开。** + +```sql +-- 例如现在是 2024-03-07, 周四早上 11 点。 +-- 需要调整为从周五起,以后都是凌晨 2 点开始收集统计信息,大家可以这样进行设置: + +call dbms_scheduler.set_attribute( + 'FRIDAY_WINDOW', 'NEXT_DATE', '2024-03-08 02:00:00'); + +call dbms_scheduler.set_attribute( + 'SATURDAY_WINDOW', 'NEXT_DATE', '2024-03-09 02:00:00'); + +call dbms_scheduler.set_attribute( + 'SUNDAY_WINDOW', 'NEXT_DATE', '2024-03-10 02:00:00'); + +call dbms_scheduler.set_attribute( + 'MONDAY_WINDOW', 'NEXT_DATE', '2024-03-11 02:00:00'); + +call dbms_scheduler.set_attribute( + 'TUESDAY_WINDOW', 'NEXT_DATE', '2024-03-12 02:00:00'); + +call dbms_scheduler.set_attribute( + 'WEDNESDAY_WINDOW', 'NEXT_DATE', '2024-03-13 02:00:00'); + +call dbms_scheduler.set_attribute( + 'THURSDAY_WINDOW', 'NEXT_DATE', '2024-03-14 02:00:00'); + +``` +> 注意:以上命令仅适用于 OceanBase MySQL 模式的租户。 + +### 存在超大的业务表导致统计信息收集不完 +统计信息收集的默认策略下,需要单并发全表扫描待收集统计信息的表 / 分区。如果待收集统计信息的表 / 分区的数据量特别大,或者占用的磁盘空间特别多,会导致统计信息收集耗时过长影响后续其它表的统计信息收集,甚至当前表可能会收集超时。 + +当业务环境下存在数据量过亿,或者磁盘空间占用超过 20G 的表时,建议参考以下方案配置统计信息收集策略: + +1. 跳过大对象。 + +例如 MySQL 租户模式下默认会收集 longtext 列的统计信息,如果 longtext 列中保存的都是大对象,可能会导致收集特别慢。 + +如下示例中,第四个参数配置收集哪些列的统计信息,需要把除大对象外的所有列都加上。 + +```sql +call dbms_stats.set_table_prefs( + 'databse_name', + 'table_name', + 'method_opt', + 'for columns col1,col2,col3,... size auto'); +``` + +2. 提高并行度或配置块采样。 + +提高并行度可以让统计信息收集时使用更多的线程同时收集,通过消耗更多的资源达到快速收集的效果。 + +使用块采样,可以通过采样的方式减少统计信息收集时要处理的数据量,也达到到快速收集的效果。 + +两者都可以达到提升统计信息收集效率的,区别于并行的方式通过牺牲资源保证了统计信息的准确性,采用的方式通过牺牲统计信息的准确性保证了系统资源的可用性。 + +用户可根据自己的需要选择其中一种方式进行配置。 + +```sql +-- 配置统计信息收集时的并行度 +call dbms_stats.set_table_prefs( + 'databse_name', + 'table_name', + 'degree', + '4'); +``` + +```sql +-- 配置开启块采样 +call dbms_stats.set_table_prefs( + 'databse_name', + 'table_name', + 'block_sample', + 'True'); + +-- 配置采样比例,可根据表的数量级进行配置,通常情况下,采集千万级的数据即可充分反应一个表的数据特征 +call dbms_stats.set_table_prefs( + 'databse_name', + 'table_name', + 'estimate_percent', + '0.1'); +``` + +3. 对于分区表,可以考虑不收集全局统计信息。如下示例中,第四个参数配置收集什么级别的统计信息,一级分区表可配置只收集一级分区的统计信息,二级分区表可配置只收集二级分区的统计信息。需要注意的是,如果采用这种策略,需要删除全局统计信息(一级分区表和二级分区表时)和一级分区统计信息(仅二级分区表时)。 + +```sql +-- 一级分区表 +call dbms_stats.set_table_prefs( + 'databse_name', + 'table_name', + 'granularity', + 'PARTITION'); + +-- 二级分区表 +call dbms_stats.set_table_prefs( + 'databse_name', + 'table_name', + 'granularity', + 'SUBPARTITION'); +``` + +### 跑批场景导完数据直接查询缺失统计信息 +默认情况下自动收集统计信息的任务是定时触发的,在此之外统计信息是不会更新的。 + +> 在 4.2.4 及 4.2.5 以上版本版本的 OBServer 中,提供了异步统计信息收集的能力,可以解决这个问题。 + +如果业务存在空表或小数据量的表导入大量数据后立即查询的场景(通常存在于跑批场景中),优化器会因为缺失统计信息或统计信息严重过期生成不优的计划。 + +**针对这种场景,建议业务在批量导入数据后手动进行一次统计信息收集后再进行业务查询。如果导入的数据非常大,可以调整一下手动收集的策略,详细可以参考本文中的 “存在超大的业务表导致统计信息收集不完” 部分。** + +### 按日期预建分区的数据表查询当天导入的数据 + +对于按日期预建分区的数据表,部分预建的分区收集统计信息时是不存在任何数据的,优化器看到的这些分区的统计信息为 0 行。 + +一旦某个分区当天导入了新的数据,并且业务逻辑会查询这一天新导入的数据,优化器就极容易因为统计信息严重过期而生成不优的计划。 + +针对这种场景,建议在当天数据导入后手动收集一次对应分区的统计信息。 + +> 在 4.2.4 及 4.2.5 以上版本版本的 OBServer 中,提供了异步统计信息收集的能力,可以解决这个问题。 \ No newline at end of file diff --git a/docs/user_manual/operation_and_maintenance/operations_and_maintenance/optimizer_statistics/03_command.md b/docs/user_manual/operation_and_maintenance/operations_and_maintenance/optimizer_statistics/03_command.md new file mode 100644 index 000000000..dcef536d4 --- /dev/null +++ b/docs/user_manual/operation_and_maintenance/operations_and_maintenance/optimizer_statistics/03_command.md @@ -0,0 +1,219 @@ +--- +title: 手动统计信息收集命令手册 +weight: 3 +--- + +> 说明:目前 DBA 进阶教程的内容暂时对应的是 OceanBase 4.x 社区版本,本小节的架构部分不涉及商业版中 Oracle 模式租户下的内容。社区版和商业版的能力区别详见:[官网链接](https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000001428510)。 + +优化器统计信息是一个描述数据库中表和列信息的数据集合,是选取最优执行计划非常关键的部分。 + +OceanBase 4.x 之前版本的统计信息收集主要依靠每日合并过程中完成,但是由于每日合并是增量合并,会导致统计信息并不是一直准确的,同时每日合并也没法收集直方图信息。 + +因此,从 OceanBase 4.x 版本开始,实现了全新的统计信息收集,将统计信息收集和每日合并解耦,每日合并不再收集统计信息。所以在使用 OceanBase 4.x 版本的时候需要特别关注统计信息的收集情况。 + +本文将结合一些实际应用场景针对性的推荐一些手动统计信息收集的命令。 + +## 表级统计信息收集 +如果需要显示收集某个表的统计信息,当前主要提供了两种方式进行统计信息:**DBMS_STATS 系统包和 ANALYZE 命令行**。不同版本的差异如下: + +### 非分区表的统计信息收集 +**当表的数据量和列的个数的乘积不高于 1 千万时**,推荐使用如下命令收集,比如如下 TEST 用户的表 T1 有 10 个列,同时数据量在一百万行时: + +```sql +create table test.t1( + c1 int, c2 int, c3 int, c4 int, c5 int, + c6 int, c7 int, c8 int, c9 int, c10 int); + +insert /*+append*/ into t1 + select level,level,level,level,level, + level,level,level,level,level + from dual + connect by level <= 1000000; +``` + +```sql +-- re1.不收集直方图 + +call dbms_stats.gather_table_stats( + 'test', + 't1', + method_opt=>'for all columns size 1'); + + +-- re2.直方图收集使用默认策略 + +call dbms_stats.gather_table_stats('test', 't1');、 + +-- 收集时间约 2 秒左右 +``` + +**当表的数据量和列的个数的乘积超过 1 千万时**,推荐可以根据数据业务情况和系统资源设置一定并行度加快统计信息的收集。比如,上述 TEST 用户 T1 表中的数据量增大到 1 千万行,使用 8 个并行度: + +```sql +create table test.t1( + c1 int, c2 int, c3 int, c4 int, c5 int, + c6 int, c7 int, c8 int, c9 int, c10 int); + +insert /*+append*/ into t1 + select level,level,level,level,level, + level,level,level,level,level + from dual + connect by level <= 10000000; +``` + +```sql +-- re1.不收集直方图 + +call dbms_stats.gather_table_stats( + 'test', + 't1', + degree=>8, + method_opt=>'for all columns size 1'); + +-- re2.直方图收集使用默认策略 + +call dbms_stats.gather_table_stats( + 'test', + 't1', + degree=>8); + +-- 收集时间约 4 秒左右 +``` + + +### 分区表的统计信息收集 +相比较于非分区表,统计信息收集需要考虑分区表的分区统计信息收集,因此收集策略配置时需要将其考虑进去。 + +**在系统资源允许的情况下,推荐在上述收集非分区表的并行度情况下再额外增加一倍的并行度**,相同上述场景中的 TEST 用户 T1 表改为 128 分区的 T_PART 表,10 列,100 万行数据,由于多了一个分区统计信息的收集,因此加了并行度为 2 : + +```sql +create table t_part( + c1 int, c2 int, c3 int, c4 int, c5 int, + c6 int, c7 int, c8 int, c9 int, c10 int +)partition by hash(c1) partitions 128; + +insert /*+append*/ into t_part + select level,level,level,level,level, + level,level,level,level,level + from dual + connect by level <= 1000000; +``` + +```sql +-- 使用适当的并行度: + +-- re1.不收集直方图 + +call dbms_stats.gather_table_stats( + 'test', + 't_part', + degree=>2, + method_opt=>'for all columns size 1'); + +-- re2.直方图收集使用默认策略 + +call dbms_stats.gather_table_stats( + 'test', + 't_part', + degree=>2); + +-- 收集时间约 4 秒左右 +``` + + +针对分区表,除了上述增加并行度以外,**我们也可以考虑分区推导方式的收集,即收集分区的统计信息,进而通过分区统计信息推导全局统计信息**,加快收集的效率,比如同样的上述场景,不增加并行度,调整收集的分区方式: + +```sql +-- re1.不收集直方图 + +call dbms_stats.gather_table_stats( + 'test', + 't_part', + granularity=>'APPROX_GLOBAL AND PARTITION', + method_opt=>'for all columns size 1'); + +-- re2.直方图收集使用默认策略 + +call dbms_stats.gather_table_stats( + 'test', + 't_part', + granularity=>'APPROX_GLOBAL AND PARTITION'); + +-- 收集时间约 4 秒左右 +``` + +**综上,针对分区表的统计信息收集,可以考虑增加合适的并行度以及选择分区推导的方式进行统计信息收集**。 + + + +## SCHEMA 级别的统计信息收集 + 除了手动的对单表的统计信息收集以外,基于 DBMS_STATS 系统包还提供了对整个用户下的所有表进行统计信息。 + + 在收集某个用户下的所有表统计信息时,很显然这是一个比较耗时的操作;因此,该功能建议在业务低峰期使用。 + ++ 如果该用户下 **所有表的数据量都是一些小表(数据量不超过 1 百万行)**,可以直接使用类似于如下收集 TEST 用户的统计信息命令: + +```sql + +-- re1.不收集直方图 + +call dbms_stats.gather_schema_stats('TEST', method_opt=>'for all columns size 1'); + + +-- re2.直方图收集使用默认策略 + +call dbms_stats.gather_schema_stats('TEST'); + +``` + ++ 当收集的用户下 **存在一些大表(行数在千万级别)**时,可以在业务低峰期增大并行度来收集: + +```sql + +-- re1.不收集直方图 + +call dbms_stats.gather_schema_stats( + 'TEST', + degree=>'16', + method_opt=>'for all columns size 1'); + +-- re2.直方图收集使用默认策略 + +call dbms_stats.gather_schema_stats('TEST', degree=>'16'); +``` + ++ 如果 **用户下存在超大表(行数超过 1 亿)时,可以选择针对超大表开大并行单独收集**,然后锁定超大表的统计信息再使用上述命令收集整个用户的,收集完成后在解锁超大表的统计信息,后续按照增量模式收集;如: + +```sql +call dbms_stats.gather_table_stats( + 'test', + 'big_table', + degree=>128, + method_opt=>'for all columns size 1'); + +call dbms_stats.lock_table_stats('test','big_table'); + +call dbms_stats.gather_schema_stats( + 'TEST', + degree=>'16', + method_opt=>'for all columns size 1'); + +call dbms_stats.unlock_table_stats('test','big_table'); +``` + +## 统计信息过期查询 + +以下 SQL 仅适用于 OceanBase 4.2 及其之后版本: +```sql +select distinct DATABASE_NAME, TABLE_NAME + from oceanbase.DBA_OB_TABLE_STAT_STALE_INFO + where DATABASE_NAME not in('oceanbase','mysql', '__recyclebin') + and (IS_STALE = 'YES' or LAST_ANALYZED_TIME is null); +``` + +```cpp +select distinct OWNER, TABLE_NAME + from sys.DBA_OB_TABLE_STAT_STALE_INFO + where OWNER != 'oceanbase' + and OWNER != '__recyclebin' and (IS_STALE = 'YES' or LAST_ANALYZED_TIME is null); +``` \ No newline at end of file diff --git a/docs/user_manual/operation_and_maintenance/operations_and_maintenance/optimizer_statistics/_category_.yml b/docs/user_manual/operation_and_maintenance/operations_and_maintenance/optimizer_statistics/_category_.yml new file mode 100644 index 000000000..7109b5f5d --- /dev/null +++ b/docs/user_manual/operation_and_maintenance/operations_and_maintenance/optimizer_statistics/_category_.yml @@ -0,0 +1 @@ +label: 统计信息使用指南 \ No newline at end of file