SQL 调优的目的

SQL 调优是指通过综合分析 SQL 的执行计划、执行监控信息、系统配置、系统负载等多方面因素,调整 SQL 的执行策略,以使其符合预期的过程。

SQL 调优的目的一般可分为两类:

  • 单一的 SQL 调优

    单一的 SQL 调优的优化主体是被调试的 SQL 执行本身,一般调优的目标包括该 SQL 的执行时间、试行期的资源消耗等。常见的调优手段包括调整访问路径、执行顺序、逻辑改写等。

  • 系统的 SQL 调优

    系统的 SQL 调优的目的是提高整个系统的吞吐量或者系统利用率等。系统的 SQL 调优过程往往比较复杂,需要综合分析当前系统的负载特征,结合多条 SQL 的执行计划,分析问题的症结所在。其关注的问题也往往超越了单条 SQL 的执行计划,例如热点行竞争、buffer cache 命中率等全局性的调优点。

问题来源

用户 SQL 写法未遵循 OceanBase 数据库开发规范

用户 SQL 的写法对 SQL 的执行性能有决定性的作用。在使用过程中,用户应尽量遵循 OceanBase 数据库开发规范的要求。

代价模型缺陷导致的执行计划选择错误

OceanBase 数据库内建的代价模型是 server 的固有逻辑,最佳的执行计划依赖此代价模型。因此,一旦出现由代价模型导致的计划选择错误,用户只能通过执行计划绑定来确保选择“正确”的执行计划。

数据统计信息不准确

查询优化过程依赖数据统计信息的准确性,OceanBase 数据库优化器默认会在数据合并过程中收集一些统计信息,当用户对数据进行了大量修改时,可能会导致统计信息落后于真实数据的特征,用户可以通过发起每日合并,主动更新统计信息。

除了优化器收集的统计信息以外,优化器还会根据查询条件对存储层进行采样,用以后续的优化选择。OceanBase 数据库目前仅支持对本地存储进行采样,对于数据分区在远程节点上的情况,只能使用默认收集的统计信息进行代价估计,可能会引入代价偏差。

数据库物理设计降低查询性能

查询的性能很大程度上取决于数据库的物理设计,包括所访问对象的 schema 信息等。例如,对于二级索引,如果所需的投影列没有包括在索引列之中,则需要使用回表的机制访问主表,查询的代价会增加很多。此时,可以考虑将用户的投影列加入到索引列中,构成所谓的“覆盖索引”,避免回表访问。

系统负载影响单条 SQL 的响应时间

系统的整体负载除了会影响系统的整体吞吐量,也会引起单条 SQL 的响应时间变化。OceanBase 数据库的 SQL 引擎采用队列模型,针对用户请求,如果可用线程全部被占用,则新的请求需要在请求队列中排队,直到某个线程完成当前请求。请求在队列中的排队时间可以在 (g)v$sql_audit 中看到。

客户端路由与 Server 之间出现路由反馈逻辑错误

OBProxy 的一个主要功能是将 SQL 查询路由到恰当的 server 节点。具体来说,如果用户查询没有指定使用弱一致性读属性,proxy 需要将其路由到所涉及的表(或具体分区)的主节点上,以避免 server 节点之前的二次转发;否则,proxy 会根据预先设置好的规则将其转发到恰当的节点。

由于 proxy 与 server 之间采用松耦合的方式,proxy 上缓存的数据物理分布信息刷新可能不及时,导致错误的路由选择。可能导致路由信息变化的场景有:

  • 网络不稳导致 server 间重新选主

  • 由 server上下线、轮转合并等导致的重新选主

  • 负载均衡导致重新选主

当在 SQL audit 或 plan cache 中发现有大量远程执行时,需要考虑是否与上述场景吻合。客户端与 server 之间有路由反馈逻辑,一旦发生错误,客户端会主动刷新数据物理分布信息,随后路由的选择也将恢复正常。

OceanBase SQL 调优与传统数据库的差异

OceanBase 数据库作为关系型数据库,其调优的方法与思路与传统数据库有很多相似的地方,但由于其自身特点主要有以下不同之处:

  • LSM-tree 存储引擎

    OceanBase 数据库的存储引擎采用了两层 LSM-tree 的架构,数据分为静态数据(SSTable)和动态数据(MemTable)两部分。针对写请求,用户的修改按照 BTree 的方式写入内存中的 MemTable,并定期通过合并过程合入到存储在磁盘上的 SSTable 中;针对读请求,存储引擎需要读取 MemTable、SSTable 两部分数据,合成最终的行。这一特点决定了 OceanBase 数据库在基表访问路径的代价模型上与传统数据库有较大的差异。例如,MemTable 为空(全部数据在 SSTable 中)时,存储引擎的执行效率比 MemTable 中有数据的场景的执行效率要高很多。因此,当数据合并后,SQL 的执行效率往往都有明显的提升。

    另外一个明显的例子是“buffer”表。“buffer”表是指那些被用户当做业务过程中的临时存储的数据表,这些表的访问模型往往是:写入-修改-删除,且整个过程周期很短,往往在几分钟或几个小时之内。由于 OceanBase 数据库所有的 DML 操作都是“逻辑”的,数据被删除时,并不做原地修改,而只是在内存中做“标记”删除。因此,即使用户的行被删除了,访问对应的“删除”标记也需要花费一定的时间。OceanBase 数据库对此的应对是通过 row purge,在 BTree 中标记一个范围段的删除标记以加速数据的访问。row purge 的过程是异步的,因此,当有大量删除操作后立即访问被删除的数据范围,仍然有可能遇到由于访问标记删除节点而导致的执行变慢的问题。

  • 分布式 shared-nothing 架构

    在传统的 shared-disk 数据库中,执行计划并不区分数据所在的物理节点,所有的数据访问都可以认为是“本地”的。但在 OceanBase 数据库的分布式 shared-nothing 架构中,不同的数据被存储在不同的节点上,SQL 执行计划的生成必须考虑到数据的实际物理分布,并可能因此表现出不同的性能特征。

    例如,当连接两张表时,如果两个表的数据分布在不同的物理节点上,则必然涉及节点之间的数据传输,执行计划也变为分布式执行计划,相比两表数据分布在同一个节点上的场景,执行代价必然有所增加。

    另一个常见的场景是数据的切主,由于数据的主可以在分布在不同节点上的副本之间进行切换,有可能出现之前的本地执行计划(所访问的数据在本机)变为远程执行计划或者分布式执行计划的问题,也可能增加一定的执行时间。这一问题在用户开启轮转合并功能之后尤其明显(在数据重新选主后,客户端路由可能由于数据物理位置信息刷新不及时而不准确)。