1. 背景
user库,t_third_bind_info表,third_user_id字段当前长度(varchar32)由于不满足业务发展,需变长为128上限。
2. 现象
ddl工单审批完成为14:27:
紧跟着指标波动和报错就跟着来了:
同时伴有DB的cpu、io的飚高,并伴有慢SQL:
机器指标 | |
---|---|
慢SQL | |
Sql详情 | select xxx, xxx as xxxYYY, ... from t_third_bind_info WHERE third_user_id = $1 and third_app_type = $2 and third_app_name = $3 and is_delete = false order by update_time desc limit 1 params: $1 = 'ok6VM1LvAQsd96-nKasdfasdPgT4', $2 = 'bbb_service', $3 = 'bbb_service' |
3. 分析
3.1. 是不是索引重建
首先怀疑是否是此ddl引发了索引重建,因为本身修改字段为索引字段(third_user_id),故索引不可用,进而导致了慢SQL,后经过了解到不论Mysql或Pg数据库,单纯的将索引字段从varchar(32)变为varchar(128),并不会引起索引重建, 原因如下:
3.1.1. 理论支持:
在 PostgreSQL 和 MySQL 中,如果你修改了一个已经被索引的字段的数据类型,那么相关的索引会被自动重建。
例子中,我们修改的是字段的长度,而不是修改字段的数据类型。对于这种情况,MySQL 和 PostgreSQL 的处理方式略有不同:
- 在 PostgreSQL 中,如果你将一个 VARCHAR(32) 字段修改为 VARCHAR(128),PostgreSQL 不需要重建该字段上的索引。因为这个操作只是增加了字段的最大长度,并没有改变字段的数据类型或者字段中已经存在的数据。
- 在 MySQL 中,当你更改一个字段的长度时,MySQL 会检查这个更改是否会影响到存储的数据。如果已有的数据不会被截断,MySQL 也是不会重建索引的。
请注意,尽管在这种情况下数据库可能不需要重建索引,但修改字段长度仍然可能会导致一些性能开销,因为数据库需要去检查字段中的所有数据来确认是否符合新的长度要求。而且,如果这个字段被频繁地用于查询条件,那么增加字段长度可能会影响到查询性能,因为更长的字段可能导致更多的磁盘 I/O 和更大的内存占用。
3.1.2. DBA分析:
3.2. 执行计划受到了影响
3.2.1. 索引失效
经过和DBA老师老师一起排查,首先发现到该Sql并未使用third_user_id索引,而是选择了update_time索引:
依据:日常update_time索引并不会有多少使用量,此时不仅有量,并且扫码行数很多,故推断是走到了这个索引引起了慢sql。
同时,缓存的执行计划失效了,进一步说明third_user_id索引确实没有被使用:
3.2.2. order by limit 1的坑
其实公司军规早有规定,禁止“order by xxxx limit 1,如order by update_time desc limit 1”这样的操作。 本例中,Pg数据库的order by xxx limit 1是有坑的,SQL选择了update_time作为优先索引查询,而不是xxx_id等条件字段,导致SQL实际执行时扫描的记录数增多,消耗增大,进而导致CPU升高。
在一些特殊情况下,如本例,因为字段变长,走条件索引的代价比较高,然后走了索引消除排序,选择了比较差的执行计划,之后DBA老师重新analyze表又生成了一次执行计划才正确。
从设计原理上没有问题,实际执行有问题,消除排序的话在一定条件下是有利的,前提是数据非常均匀的分布。
故,DBA还是建议改造相关查询,尽量不用order by xxx limit 1 !
4. 总结
最终根因,DDL修改索引字段(third_user_id)由varchar(32)变为varchar(128),字段变长会影响统计信息(比如数据的分布,数据的基数等),进而影响执行计划偏离third_user_id索引;再加上order by xxx limit 1 的问题,使执行计划将目光锁定在了update_time索引,导致扫描行增多,短时间产生大量慢SQL。