×
技术社区 >  技术博客 >  一条生产库上 “低风险” 的 SQL,藏着多少性能细节?

一条生产库上 “低风险” 的 SQL,藏着多少性能细节?

在生产数据库运维、数据质量摸底、测试数据清理前的预估场景中,我们经常需要快速统计某类数据量,但又怕全表扫描拖垮数据库。

今天就带大家拆解一条专为生产环境设计的采样统计 SQL,从 Hint 优化、模糊查询细节,到数据库采样原理,一次性讲透所有技术点!

sql 解释:

sql 文本:

SELECT /*+ NO_REWRITE NO_PARALLEL  DYNAMIC_SAMPLING(0)  QUERY_TIMEOUT(10000000)  OPT_PARAM('USE_DEFAULT_OPT_STAT','TRUE') */ 
SUM(CASE WHEN  (`APPNT_NAME_xxx` like '%测试%' escape '\\') THEN 1 ELSE 0 END) 
FROM `exx_xxx`.`exx_xxx`   SAMPLE BLOCK(0.003507)  SEED(1)   `a`   

hint 部分

Hint	作用
NO_REWRITE	禁止查询改写,保持 SQL 原样执行
NO_PARALLEL	禁止并行执行,使用单线程
DYNAMIC_SAMPLING(0)	关闭动态采样,不在执行时额外采集统计信息
QUERY_TIMEOUT(10000000)	设置查询超时为 10,000,000 微秒(10 秒)
OPT_PARAM('USE_DEFAULT_OPT_STAT','TRUE')	使用默认的优化器统计信息

主要查询

SUM(CASE WHEN (`APPNT_NAME_xxx` like '%测试%' escape '\\') THEN 1 ELSE 0 END)

对 APPNT_NAME_xxx 字段做模糊匹配,查找包含 "测试" 的记录

escape '\' 指定转义字符为反斜杠(处理特殊字符)

匹配到则计 1,否则计 0,最终 SUM 得到符合条件的记录总数

数据来源

FROM `exx_xxx`.`exx_xxx` SAMPLE BLOCK(0.003507) SEED(1) `a`

exx_xxx.exx_xxx:契约表

SAMPLE BLOCK(0.003507):块级采样,只扫描约 0.3507% 的数据块(而非全表扫描),大幅减少 IO

SEED(1):固定随机种子为 1,确保每次采样结果可重复

a:表别名

总结:

这条 SQL 的目的是:通过采样(约 0.35% 的数据块)快速估算 exx_xxx 表中投保人姓名包含"测试"二字的记录数。这通常用于数据质量检查或测试数据清理前的摸底统计,用采样代替全表扫描以降低对生产库的性能影响。

技术点解析:

hint 作用:

NO_REWRITE 禁止查询改写,保持 SQL 原样执行

NO_PARALLEL 禁止并行执行,使用单线程

DYNAMIC_SAMPLING(0) 关闭动态采样,不在执行时额外采集统计信息

QUERY_TIMEOUT(10000000) 设置查询超时为 10,000,000 微秒(10 秒)

OPT_PARAM('USE_DEFAULT_OPT_STAT','TRUE') 使用默认的优化器统计信息

模糊匹配:

其中模式字符串支持两个通配符:

通配符 含义 示例
% 匹配任意长度的任意字符(包括零个字符) '%测试%' 匹配任何位置包含"测试"的字符串
_ 匹配恰好一个任意字符 '张_' 匹配"张三"、"张四",但不匹配"张三丰"

性能影响

LIKE '测试%'   -- 可以利用索引(前缀匹配)
LIKE '%测试%'  -- 无法利用索引(需逐行扫描)
LIKE '%测试'   -- 无法利用索引(需逐行扫描)

ESCAPE '\\' 的作用

ESCAPE 子句定义一个转义字符,使通配符 %_ 可以被当作普通字符匹配。

这里 '\\' 是 SQL 字符串中反斜杠的写法(\ 本身需要转义),所以实际转义字符是 \

举例说明:如果要搜索 USER_NAME 中真的包含 % 字符(而不是通配):

-- 不用 ESCAPE:% 被当作通配符,匹配所有以"折扣"开头的
USER_NAME LIKE '折扣%'

-- 用 ESCAPE:\% 表示字面量 %,精确匹配包含"折扣%"这个字符串的
USER_NAME LIKE '%折扣\%%' ESCAPE '\\'
--               ↑    ↑↑
--               |    |└── 通配符%(匹配任意后缀)
--               |    └─── \% 被转义为字面量 %
--               └──────── 通配符%(匹配任意前缀)

用 # 作为转义字符(证明不一定要用反斜杠)

SELECT * FROM products 
WHERE product_name LIKE '%#%%' ESCAPE '#';
-- #% → 字面量 %
-- 效果和前面用 \ 完全一样

案例 3:同时搜索 % 和 _

找名称中同时包含 %_ 的数据:

SELECT * FROM products_xx 
WHERE product_name LIKE '%\%%' ESCAPE '\'
  AND product_name LIKE '%\_%' ESCAPE '\';

SAMPLE BLOCK(0.003507)

数据库不是一行一行存数据的,而是把很多行打包成一个"块"(Block/Page)存在磁盘上。

磁盘上的数据组织方式:

┌─────────────┐  ┌─────────────┐  ┌─────────────┐     ┌─────────────┐
│   Block 1   │  │   Block 2   │  │   Block 3   │ ... │  Block N    │
│  第1~100行  │  │ 第101~200行 │  │ 第201~300行 │     │  最后几行   │
└─────────────┘  └─────────────┘  └─────────────┘     └─────────────┘

全表扫描 = 读取所有 Block = 全部 IO 开销;

SAMPLE BLOCK 做了什么?

不读所有 Block,只随机抽取约 0.003507% 的 Block 来读。

全部 Block(假设有 100,000 个):
■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■... (100,000个)

SAMPLE BLOCK(0.003507) 后实际读取的(约 3~4 个):
□□□□□■□□□□□□□□□□□□□□□□■□□□□□□□□□□■□□□□... 
          ↑                    ↑               ↑
       被选中               被选中           被选中

只有被选中的 Block 才会从磁盘读出来,其余全部跳过

SEED(1) 的作用

SAMPLE BLOCK(0.003507) SEED(1)

采样是随机的,那每次执行选中的 Block 可能不同,结果就会变化。SEED(1) 就是固定随机数种子为 1,保证:

第一次执行:随机选中 Block 5, 1023, 8847, ... → 结果:42

第二次执行:随机选中 Block 5, 1023, 8847, ... → 结果:42(一模一样)

第三次执行:随机选中 Block 5, 1023, 8847, ... → 结果:42(还是一样)

如果去掉 SEED:

第一次执行:随机选中 Block 5, 1023, 8847, ... → 结果:42

第二次执行:随机选中 Block 77, 555, 9012, ... → 结果:38(变了)

第三次执行:随机选中 Block 200, 3001, 7788, ... → 结果:45(又变了)

SEED 的好处:结果可重复、可验证,别人用同样的 SQL 能得到一模一样的数。

SAMPLE BLOCK vs SAMPLE ROW

OceanBase/Oracle 支持两种采样方式:

方式 语法 粒度 速度 精度
块采样 SAMPLE BLOCK(n) 以 Block 为单位抽取 快(跳过整个 Block) 略低(Block 内数据可能分布不均)
行采样 SAMPLE(n) 以行为单位抽取 慢(需要逐行判断是否抽中) 较高
块采样 SAMPLE BLOCK(n):
  读 Block 5 的全部行 → 读 Block 1023 的全部行 → ...
  ↑ 整块读,IO 连续,磁盘效率高

行采样 SAMPLE(n):
  Block 1: 第1行(不要) 第2行(要) 第3行(不要) ...
  Block 2: 第1行(要) 第2行(不要) ...
  ↑ 每个 Block 都得读,只是行级筛选,IO 没减少

本 SQL 用的是 SAMPLE BLOCK,追求的是速度优先,因为目的只是粗略估算"测试"数据的数量,不需要精确到个位数.

采样结果:

采样返回的是部分数据的统计值,要估算全表数据需要换算:

假设采样结果:SUM = 3(在采样的 Block 中找到 3 条包含"测试"的记录)

采样比例:0.003507%

估算全表数量 = 3 / 0.003507% = 3 / 0.00003507 ≈ 85,543 条

这是一个估算值,实际数字可能有偏差,但量级是对的;

精选推荐