数据库内核月报

数据库内核月报 - 2019 / 11

MySQL · 最佳实践 · 今天你并行了吗?---洞察PolarDB 8.0之并行查询

Author: jijunxiang

前言

今天你并行了吗?在PolarDB 8.0中,我们领先MySQL官方版本,率先支持了SQL的并行执行,充分利用硬件多核多CPU的优势,大幅提高了类OLAP的查询性能,体现了自研PolarDB数据库极高的性价比。

如何深入分析SQL的并行执行情况,以协助DBA解决可能存在的性能瓶颈,是一个迫在眉睫的问题,因此PolarDB 8.0在Performance Schema中添加了对并行查询的支持,可以帮助DBA洞察并行执行过程中的各种情况,为后续的查询优化提供丰富的情报。下面我们就详细了解一下并行查询的分析过程。

准备工作

打开并行查询

image.png

打开performance_schema

image.png

注意: 打开performance_schema 需要重启实例, 因此用户请在合适时间进行设置

开启并行查询相关的消费者consumer

后续操作都是由实例的超级用户,或者库performance_schema 已经授权给当前用户

update performance_schema.setup_consumers
set ENABLED= 'YES'
where name like 'events_parallel_query_current' ;
update performance_schema.setup_consumers
set ENABLED= 'YES'
where name like 'events_parallel_operator_current' ;
update performance_schema.setup_consumers
set ENABLED= 'YES'
where name like 'events_parallel_query_history' ;
update performance_schema.setup_consumers
set ENABLED= 'YES'
where name like 'events_parallel_operator_history' ;

启动时自动开启对并行查询相关事件的监测。

在my.cnf中添加以下行:

performance-schema-instrument='parallel_query%=ON'

性能分析

(1)检测是否并行

      当出现性能问题时,首先我们要确定查询是否真的是并行执行。虽然通过查看执行计划,我们可以看到执行计划是并行的执行计划,但这并不意味着在执行时一定会并行执行,不能并行执行的原因通常是资源不足,CPU、内存、线程等资源的限制都要求我们不能无限制的并行。如果并行不受限制的执行,可能会导致一个并行查询就耗光了所有的资源,从而导致其它所有任务都被堵塞,可用性也受到极大影响。

        因此针对这个问题,系统提供了一个参数max_parallel_workers来限制同时执行的并行数量。max_parallel_workers指在同一时刻用于并行执行的worker线程的最大数目(系统硬性设置为实例cpu数的4倍)。当超过max_parallel_workers时,之后就不会再产生并行执行计划,即使这些查询的执行计划中并行执行计划最优。

        我们不能实时的去查询每个执行计划,因此当查询执行完成后,需要一些方法去确定刚刚执行完成的查询是否以并行方式执行。在performance schema中的events_statements_current/events_statements_history可以查看某条查询是否以并行方式执行。

      如果发现当前情况下, 原本预期应该是并行的查询,但变成非并行查询, 需要进一步分析

查询当前连接的内部thread_id:

select thread_id from performance_schema.threads where processlist_id=connection_id();

然后执行以下SQL:

select th.thread_id,
       processlist_id,
       SQL_TEXT,
       PARALLEL
       from performance_schema.events_statements_history stmth,
       performance_schema.threads th
       where stmth.thread_id= th.thread_id
       order by PARALLEL desc;

下图是在控制台dms上的输出结果 image.png

找到你关注的SQL 的thread_id, PARALLEL 状态。 

注意:

select thread_id from performance_schema.threads where processlist_id=connection_id();

如想查询在session-1上执行的并行查询信息:
1)在session-1上执行以下语句,得到session-1的connection_id=2143;
select connection_id();
2)  在session-1上执行并行查询
select sum(c3), c4 from test.t1 where c3 > 5000 group by c4;
3)  在session-2上查询session-1刚刚执行的并行查询的执行信息

select processlist_id,
       SQL_TEXT,
       PARALLEL
       from performance_schema.events_statements_current stmt,
       performance_schema.threads th
       where stmt.thread_id= th.thread_id
       and processlist_id= 2143\G

结果如下所示:

(2)查看并行查询的运行时信息

当我们确定一个查询以并行方式执行时,也许更想知道到底这个查询的并行执行过程是怎样的?有多少个并行算子?有多个并行执行的线程?每个线程都做了什么,扫描多少数据,又返回了多少数据?……
        通过查询performance schema中的events_parallel_query_xxx和events_parallel_operator_xxx可以为你提供以上的所有信息。

events_parallel_query_xxx中保存并行查询在执行过程中的运行时信息,参与执行的每个线程,包括session线程和worker线程,每个线程都有自己的并行执行信息。因此一条并行查询会有多条并行执行信息。

当并行查询执行正在执行或执行完成后,可以通过events_parallel_query_xxx来查看并行执行的相关信息。

select ph.thread_id,
ph.event_id,
sh.parallel,
ph.timer_wait/1000000000000 as exec_time,
sh.sql_text,
ph.state,
ph.*
from performance_schema.events_parallel_query_history ph,
performance_schema.events_statements_history sh
where ph.thread_id= sh.thread_id
and ph.thread_id= 87267   /* 替换为前面查询的thread_id */
and ph.nesting_event_id = sh.event_id
\G

表events_parallel_query_xxx中包含以下内容:

举例来说,
13.png
14.png
其中:

(3)查看并行算子的运行时信息

events_parallel_operator_xxx表中提供并行查询中并行算子的相关信息,所谓并行算子,指可以由多个线程并行执行完成的任务,比如gather,gather会创建一组worker线程,由一组worker线程共同来完成扫描任务。

并行算子在执行计划中也可以查看到,如下所示:
explain select sum(c3), c4 from test.t1 where c3 > 5000 group by c4;

        其中就是一个并行算子,它会执行Parallel scan,计划由32workers来完成。当然受限于资源的限制,实际上worker的数量可能会少于计划的32个workers.
        当查询执行计划过程中或完成后,可以通过events_parallel_operator_current查看最近执行语句的并行算子相关信息。

select *
from performance_schema.events_parallel_operator_history
where thread_id= xxx /* 替换为前面查询的thread_id*/

表events_parallel_operator_xxx中包含以下内容:

15.png

在本例中

(4)查看worker的运行时信息

    每个并行算子可以有多个worker,每个worker独立完成部分子任务,如扫描某个表的若干个分片。每个worker也都有自己的运行时信息,这些信息也是保存在events_parallel_query_xxx中,只是角色是worker而已。
        因为worker是关联到并行算子的,所以可以通过并行算子的event_id和thread_id来过滤查询,
/纪君祥, 原文中, 有/

select *
from performance_schema.events_parallel_query_current
where parent_thread_id= xxx /*前面查询的thread_id*/
UNION
select *
from performance_schema.events_parallel_query_history
where parent_thread_id= xxx \G; /*前面查询的thread_id*/

16.png
其中:

(5)查看内存消耗

select *
from performance_schema.memory_summary_global_by_event_name
where event_name like '%parallel%' \G




    其中:

  1. memory/performance_schema/parallel_query_class指为parallel_query事件类分配的内存,目前只有3种parallel_query事件类型,role/leader,role/worker及operator/gather,主要用于保存事件类的各种属性。
  2. memory/performance_schema/parallel_query_objects指为记录并行查询执行过程中的运行时信息所需对象所分配的内存,以page为单位分配,每个page包含128个parallel_query对象,此内存一经分配,除非系统重新启动,不能被释放。
  3. memory/performance_schema/parallel_operator_objects指为记录并行算子执行过程中的运行时信息所需对象所分配的内存,以page为单位分配,每个page包含128个parallel_operator对象,此内存一经分配,除非系统重新启动,不能被释放。
  4. memory/performance_schema/parallel_query_history指为保存历史并行查询的运行时信息所分配的内存,此处只为其分配存储指针的内存,默认每个线程10条历史记录,与线程对象同时分配。此内存一经分配,除非系统重新启动,不能被释放。
  5. COUNT_ALLOC指分配的次数,对于事件相关对象是可变的,当新分配page时,自动加1,此数值只能单调递增。
  6. CURRENT_NUMBER_OF_BYTES_USED指当前已经分配的内存,单位是byte。

    注意:

(6)查看parallel query 对象

每个并行查询的并行度可能都不一样,每个并行查询包含的并行算子个数可能都不一样,因此每个并行查询所需记录运行时信息的parallel query对象和operator对象并不确定,为防止无限制的使用内存,系统做了以下限制:

此外,可以查看并行对象的使用情况,如
show status like '%objects_used';

总结

首先,我们需要了解的是,并行并不一定是最好的执行计划,在许多场景下,单线程顺序执行也许效率更高,执行更快。如表数据比较少,或能分配到worker上执行的任务比较少,或资源不足等。另外目前对并行的支持还有很多需要提升的地方,不是每个场景都适合并行执行。所以不要因为开启了并行,就期待对所有场景都有极大的提升,性能的提升还是需要DBA不断的去分析、去优化,并行执行只是其中的一个手段而已。

并行查询的支持只是一个开始,让我们期待PolarDB有更多更好的自研特性出来,为客户提供更美好的用户体验。