相信大家使用MySQL时一定碰到了不少慢查询相关的问题,为了优化这些慢SQL,往往需要费劲心思琢磨执行计划、考虑建索引、改写SQL,甚至是改写程序逻辑。根据具体SQL的优化既很有复杂度,也会有很多场景无论如何优化也没有取得很好的效果。
典型场景
下面是2个典型的慢查询场景,大家在日常写SQL和优化SQL的过程中应该会有感受:
场景一:大表。
这是MySQL的经典问题。MySQL早期有单表数据量不能超过2000W的说法,虽然有点夸张,不过对于MyQL中千万级,乃至过亿的表,哪怕对这种表的查询建了索引,索引的B+树也会很深,查询速度确实很受影响,从而形成慢SQL。
一些有经验的开发会考虑给这张表加分区,通过合理拆分,来降低每个分区对应B+树索引的深度,从而提升执行性能。但MySQL毕竟是一个单机库,各个分区共享的还是一份硬件资源;而如果想突破单机的限制,就要考虑做分库,而那又是一个更复杂的问题,涉及到整个数据库用法和结构的大调整。
场景二:多表连接。这也是让很多研发头痛的问题,类似下面这类SQL:
select ...
from t1 --100行
left join t2 --100000行
on t1.c1=t2.c1
left join t3 --100行
on t1.c1=t3.c1
left join t4 --100行
on t1.c1=t4.c1
order by t1.c1 limit 1000;
在这个例子中, 4 张表做连接 T1、T2、T3、T4,其中 T2 是大表,其它都是小表,如果严格按照连接次序来做,T1 跟 T2 连接,再跟 T3、T4 连接,最大的表T2就过早进行了合并,导致结果集特别大,整个SQL开销相对较高。
如果要优化慢查询的话,就要改写SQL,把T2挪到最后来连接,这样整个SQL的代价就会小很多了,但很多时候这是跟我们业务开发的逻辑是违背的。很多场景下,修改SQL都是非常麻烦的一件事。
其实自适应调整表连接顺序应该是数据库能做到的,这步一般被称为“SQL改写”,但因为MySQL当前的优化器更多是基于规则的模型,所以通常情况是做不到这点的。
为什么MySQL的大SQL能力比较差?
因为MySQL社区主要还是将MySQL定位为一个纯粹OLTP(Online Transaction Processing,联机交易处理)型的开源数据库。这个定位使得MySQL的迭代发版,侧重于提升单核性能、加强事务处理能力等,而对于大数据量、复杂查询类的偏OLAP(Online Analytical Processing,联机事务处理)场景,MySQL发展缓慢。
举个例子:在MySQL 8.0.14前,MySQL是没有并行执行能力的。也就是说MySQL对每条SQL最多只能使用CPU的一个核来处理。而并行执行可以将一个 SQL 查询任务分成多个子任务,并允许这些子任务在多个处理器上同时运行,以提高整个查询任务的执行效率。对于上文的慢查询场景一而言,并行执行就可以极快加速这种场景的查询效率。虽然MySQL自8.0.14版本开始支持并行执行,但目前仅在select count(*)等有限场景下生效,且必须手动配置参数。因此目前MySQL还不支持大部分场景的并行执行,从根本上解决复杂查询的问题。
用分布式数据库解决慢查询问题
针对这些挑战,OceanBase作为一款原生分布式的HTAP(Hybrid Transactional/Analytical Processing)数据库,对大规模查询场景的处理上进行了重点的优化。如果您也在寻求解决慢SQL查询的方案,不妨尝试一下用分布式数据库OceanBase从根本上来解决这个问题。
OceanBase在大查询场景的主要技术点有:
1、并行执行:OceanBase有非常成熟的并行执行能力,可以对普通查询、DDL、DML操作都进行并行执行,并且可以自动/手动灵活选取并行度。这可以使得开发者通过少量CPU的代价,简单、直接的使原本运行较慢的SQL性能快上几倍,对于大SQL问题有立竿见影的解决效果。
可以通过下面两个参数来开启并设置自动并行:
#开启并行
set global parallel_degree_policy = AUTO;
#设置基表最大扫描时间,单位为ms,默认为1000ms;您可以视情况调大或调小这个值,这里把这个参数改为100ms,即基表扫描时间超过100ms,则开启并行执行。
set global parallel_min_scan_time_threshold = 100;
2、分库分表:如同前文MySQL的场景一中所提到的,解决大数据量表查询性能问题的一个解法是分库分表,但MySQL分库分表有很高的改造成本。而OceanBase是一款原生的分布式数据库,OceanBase中的分区是一个独立的存储、高可用、事务的单位,这意味着OceanBase同一张表的不同分区可以分布在不同的服务器上,从而利用多机性能大大加快大表查询速度。同时OB的原生分布式能力,也可以使应用程序像使用一个单机数据库一样使用分布式的OB,没有业务改造成本。
3、多表连接次序优化:对于前文的场景二,业务上是比较难判断怎么调整表连接的顺序更好的,而OceanBase 实现了一套完备的连接枚举算法,整体优化逻辑是基于代价的,可以灵活调整内连接和外连接的次序,可以调整外连接、反连接、半连接,甚至还可以改变一个外连接的连接类型,把它变成内连接或反连接等,可以做到场景自动优化。像场景二的例子,在OB中自然就会优化成,t1跟t3连接,再跟t4连接,最后再跟t2连接,从而使得SQL执行性能快了近百倍。很多在MySQL中需要反复改写、调优的SQL,在OB中自然跑出来就是最优的执行计划。
4、子查询场景优化:子查询也是很常见的性能问题,并且不同于多表连接,子查询通常比较难改写,往往会造成执行很慢。OceanBase 查询改写模块,实现了丰富的子查询优化策略,只要不是嵌套非常深的子查询,都可以把它变成连接,变成连接后,就成了连接枚举的问题,可以采取不同的连接算法去优化它。
5、大表聚合场景优化:大表聚合也是一个经典的场景,比如我们要汇总一年的营业额数据,就有可能用到下面的SQL:
SELECT year(sold_date) AS yearDate, code, name, SUM(value0) as total_value FROM t where sold_date>='2023-01-01' and sold_date<'2023-12-31' GROUP BY year(sold_date), code, name;
针对这种场景,OB有一种优化能力称作预聚合,就是把大表的数据拆成多份,每个线程分别做分组聚合,然后线程交换数据继续聚合,全部聚合完后再做一次汇总。比如把示例中t表分为100组,每组按year(sold_date), code, name分别聚合,再最后汇总。
预聚合配合并行执行,往往可以取得几倍的性能提升。但这种优化并不一定是好效果的,假如一共10亿条数据,不同的year(sold_date), code, name租户就有5亿种,那么区分度就太低了,预聚合甚至是一个反优化。
OceanBase 面对大表聚合场景,是让执行引擎变得更聪明,不在优化器层面去做决策,而是交给执行层,执行根据计算过程中的实际情况,去决定做不做预聚合优化。
当前OceanBase 对各种场景基本都支持了这个优化,包括分组、去重、窗口函数,都可以灵活判断是否做预聚合。
6、TP 与 AP隔离,避免互相影响:对于数据库来说,相比于单条复杂大查询,让大量的 DML 和短查询尽快返回更有意义。为了避免一条大查询阻塞大量简单请求而导致系统吞吐量暴跌,避开分库分表的查询性能局限,当大查询和短请求同时争抢 CPU 时,OceanBase 会限制大查询的 CPU 使用。当一个线程执行的 SQL 查询耗时太长,这条查询就会被判定为大查询,一旦判定为大查询,就会进入大查询队列,然后执行大查询的线程会等在一个 Pthread Condition 上,为其它的租户工作线程让出 CPU 资源。
7、列存能力。OceanBase在最新的v4.3版本,支持了列存能力。可以支持一个节点一张表,灵活选择行存/列存/行列都存,列存可以极大程度地提升分析型SQL的性能。
https://www.oceanbase.com/docs/common-oceanbase-database-cn-1000000000819423
通过这些技术能力,可以使得原本在MySQL中执行时间超过1s的慢SQL,在OceanBase中获得显著的性能提升。
如果大家希望体验分布式数据库OceanBase在复查查询的性能,可以在OceanBase官网开通30天的免费试用。1核4G的OceanBase租户实例就可以体验查询性能,如果还需要体验OceanBase的其他特性,也可以申请企业版的集群实例。产品中有相关的新手教程引导,也请注意通过上文中的方法打开并行执行。