首页 > 数据库 >Google Spanner数据库查询优化

Google Spanner数据库查询优化

时间:2023-04-20 14:44:09浏览次数:48  
标签:Google 数据库 time 查询 索引 job trigger SQL Spanner

背景介绍
运维反馈生产环境定时任务管理界面查询速度太慢,经过定位发现,是SQL查询速度太慢导致的,经过定位发现出有以下SQL数据查询过慢

SELECT t.id, t.job_group, t.job_id, t.executor_address, t.executor_handler, t.executor_param, t.executor_sharding_param, t.executor_fail_retry_count, t.trigger_time, t.trigger_code, t.trigger_msg, t.handle_time, t.handle_code, t.handle_msg, t.alarm_status, jgroup.app_name 
FROM job_log AS t LEFT join job_group AS jgroup on t.job_group = jgroup.id WHERE t.is_deleted != 1 ORDER BY t.trigger_time DESC LIMIT 10 offset 0;

SELECT id FROM job_log WHERE ( trigger_code not in (0, 200) or handle_code = 500 ) AND `alarm_status` = 0 AND is_deleted != 1 ORDER BY id ASC LIMIT 1000;

SELECT t.id FROM job_log AS t WHERE t.handle_code = 0 and t.is_deleted != 1 and t.trigger_code = 200 and t.trigger_time <=  TIMESTAMP '2023-02-07 15:00:00' and t.executor_address not in (SELECT t2.registry_value FROM job_registry AS t2 where t2.is_deleted != 1);

SELECT count(1) FROM job_log;

数据库表当时的索引只有job_log表有两个索引,分别是

CREATE INDEX I_trigger_time ON job_log (trigger_time);
CREATE INDEX I_handle_code ON job_log (handle_code);

当时job_log的表中的数据量大概是1200W左右

问题现象
由于没办法在生产环境执行,lab环境的数据量比生产环境还多,所以我就在lab环境使用上面的SQL进行查询,发现以上四个SQL在lab环境的执行时间分别是8.58秒、5.54秒、6.64秒、4.97秒。

分析思路
由于之前一直用的是MySQL或者Oracle, Google Spanner从来没用过,还好Google有提供自带SQL查询分析,我们接下来一个个分析,以下是第一个SQL的分析图
1681881456430.jpg
我们可以看到,主要的耗时时间是在Table scan、Merge distribute union和Sort limit,其中的Table scan和Sort limit很好理解,就是全表扫描和排序过滤,那么Merge distribute union又是什么意思?

Merge distribute union:由于Google Spanner是一个分布式数据,所有的数据会存放在多个远程服务器上,当使用Order By和Limit的时候,需要从多个远程服务器上取出数据,然后再合并排序。

要解决慢SQL的问题,就要把Table scan、Merge distribute union和Sort limit问题解决,首先是Table scan,job_log表中已经有了trigger_time索引了,为什么还会走全表扫描,按照MySQL的逻辑,不是应该走trigger_time索引,从右到左扫描,然后再回主表过滤返回么?难道Google Spanner索引组织方式和M有SQL的不一样?

带着这个疑问我去看了下Google Spanner的官方文档,通过文档,我们发现,Spanner的索引是有顺序结构的,默认创建的是升序的索引,可以通过DESC来创建倒序顺序,由于我们查询的SQL是Order By trigger_time DESC,而且我们的Where条件中也没有trigger_time,所以Spanner就走了Table scan
那我们就创建一个倒序的trigger_time索引,然后再执行查询,看看效果,发现查了两分钟都没有查询出结果,后来发现是因为表连接用的是LEFT JOIN,改为JOIN后的SQL分析图
1681881519819.jpg

我们可以发现Table scan变成了Index scan,说明走了索引, 是不是以为大功告成了?然而并没有,因为是查询页面,所以会有很多查询条件,这时候我们把这个SQL改下,加个查询条件t.job_id=100000,这个job_id是不存在的,这时候再去查询发现需要79秒,比全表扫描还要慢,看SQL分析图
1681881429324.jpg
我们发现由于在trigger_time里没有job_id,所以Spanner需要回主表去判断记录是否符合,而我们查询的job_id是没有job_log,所以需要回表1200多W次,导致查询速度比全表扫描还慢,这个时候,如果是MySQL的话,就需要增加job_id的索引或者创建个联合索引,但是由于查询的条件比较多,这样创建索引会导致索引的数量太大,占用空间太多,后来发现Spanner创建索引的时候,通过STORING关键字支持带指定的列进行数据过滤,写法是

CREATE INDEX idx_trigger_time ON job_log(trigger_time DESC) STORING (job_group, job_id, handle_code, trigger_code);

最后我们执行下SQL,通过SQL分析图,可以看到不会在一条条的回表查询
1681885531409.jpg
你以为就结束了么?不不不,其实还有个问题,因为前端页面是分页查询,如果页数太多了,上面的SQL会很慢,比如我们设置limit为10,offset为100000

SELECT t.id, t.job_group, t.job_id, t.executor_address, t.executor_handler, t.executor_param, t.executor_sharding_param, t.executor_fail_retry_count, t.trigger_time, t.trigger_code, t.trigger_msg, t.handle_time, t.handle_code, t.handle_msg, t.alarm_status, jgroup.app_name 
FROM job_log AS t  join job_group AS jgroup on t.job_group = jgroup.id WHERE t.is_deleted != 1 and t.trigger_time <=  TIMESTAMP '2023-04-17 15:00:00' ORDER BY t.trigger_time DESC LIMIT 10 offset 100000;

执行改SQL,Spanner需要7.46秒才能返回数据,我们可以通过SQL分析图来看Spanner是怎么查询的
1681888678186.jpg
我们可以看到,Index scan和Table scan数量一模一样,所以可以发现Spanner是先回表在去过滤数据的,这是因为SQL的执行顺序就是FROM > WHERE > GROUP BY > HAVING > SELECT的字段 > DISTINCT > ORDER BY > LIMIT,但是我们更希望是先执行Limit再去回表,因为这样就能减少回表的次数
我们就需要把SQL改成这样

SELECT t.id, t.job_group, t.job_id, t.executor_address, t.executor_handler, t.executor_param, t.executor_sharding_param, t.executor_fail_retry_count, t.trigger_time, t.trigger_code, t.trigger_msg, t.handle_time, t.handle_code, t.handle_msg, t.alarm_status, jgroup.app_name
 FROM job_log AS t join (SELECT t.id FROM job_log AS t where t.is_deleted != 1 and t.trigger_time <=  TIMESTAMP '2023-04-17 15:00:00' ORDER BY t.trigger_time DESC LIMIT 10 offset 100000) AS log on t.id = log.id join job_group AS jgroup on t.job_group = jgroup.id ORDER BY t.trigger_time DESC;

我们再次执行优化后的SQL,发现现在只需要115.35毫秒,以下是SQL的分析图
1681889105891.png
通过SQL分析图,我们可以看到通过只查询先limit,再回表可以进一步提高SQL的性能,从8.58秒到115.35毫秒,可以提升74倍的效率,足见一个好的SQL的性能有多么重要

解决方案
最终,我们通过创建倒序索引+STORING多余的where条件的列来优化SQL,对于深分页查询,我们通过子查询的方式来做优化,其他的SQL也可以根据上面的分析逻辑来进行优化。
只有统计数据的SQL,因为我们定时任务的查询页面,在进入页面时,默认是可以没有任何条件的,所以count没办法优化,当时想的就是要不去掉count,要不默认只查最近一个星期的数据,由于如果去掉count,前端页面就没办法根据count来分页的,前端就要把分页改成只有上一页和下一页,改动较大,所以选择了第二种方式

总结
通过上面的分析,我们知道了Spanner和Mysql的一些不同,比如Spanner索引需要指定顺序,默认是倒序的,Spanner的索引可以带额外的列来过滤数据,减少回表的次数,还有一些相同点,比如Spanner的索引也是最左匹配原则
所以我们在将数据从一个数据库迁移到另一个数据库的时候,最好是要先熟悉被迁移的数据库,除了表结构之外,还需要特别注意索引的区别,因为创建索引的DDL语句都是通用的,但是每个数据库的索引组织方式都是不一样的,在这个情况下,直接迁移,虽然程序不会出错,却很容易出现SQL查询慢的问题。
在分析SQL慢查询的时候,首先要搞清楚数据库的索引组织方式,索引的一些类型,比如索引是不是区分顺序?索引能否带一些列来进行过滤?然后通过数据查询分析(基本上所有的数据库都会有)来分析为什么查询速度会这么慢

参考文档

  1. https://cloud.google.com/spanner/docs/secondary-indexes?hl=zh-cn#add-index
  2. https://cloud.google.com/spanner/docs/reference/standard-sql/data-definition-language#create-index

标签:Google,数据库,time,查询,索引,job,trigger,SQL,Spanner
From: https://www.cnblogs.com/yumaolin/p/17318983.html

相关文章

  • 数据库 分表分库
    一、分表分库1、垂直分区:根据数据库里面数据表的相关性进行拆分。例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。简单来说垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。垂直......
  • 数据库 锁
    一、行级锁,表级锁和页级锁 在关系型数据库中,可以按照锁的粒度把数据库锁分为行级锁(INNODB引擎)、表级锁(MYISAM引擎)和页级锁(BDB引擎)。      MyISAM和InnoDB存储引擎使用的锁:      MyISAM采用表级锁(table-levellocking)。      InnoD......
  • 数据库3.49到3.68例程
    3.49查询每个学生及其选修课程的情况3.50对[例33]用自然连接完成3.51查询选修2号课程且成绩在90分以上的所有学生的学号和姓名。3.52查询每一门课的间接先修课(即先修课的先修课)3.53改写[3.49查询每个学生及其选修课程的情况3.54查询每个学生的学号、姓名、选修的......
  • 记录一次使用 表达式引擎 自定义注解 还有 sql union all 实现对数据库数据提取、重组
    这样编写减少了前后端很多没必要的遍历,以及if判断并最大限度提高了代码的可变通性额外需要学习的是ORM框架下,如何接收多表(各表结构不同)操作后,sql返回的新结构的临时表问题表达式引擎用到的依赖<dependency><groupId>org.apache.commons</groupId>......
  • 关于DB2数据库基本信息查询语句
    1.1数据库的启停启动数据库:db2start关闭数据库:db2stop(在关闭前先执行db2forceapplicationall(关闭所有正在执行的连接))或者直接执行db2stopforce(不是优先选择))#--有时候db2forceapplicationall关闭不了一些active的连接时可以使用db2stopforce1.2数据库的创建及删......
  • 虹科干货 | 打破传统!金融界黑科技—虹科Redis企业版数据库
    金融行业数字化转型浪潮来袭,客户需求也正加速向在线金融服务转移。金融机构想要实现现代化改造技术堆栈,为客户提供实时交互、欺诈检测等一系列个性化创新服务,就必须重视遗留系统和传统数据库架构“老年病”问题!面对数字化颠覆带来的挑战和压力,接受变化是关键!一些极力打破传统束......
  • 数据库基础概述
    (一)数据库基础1.什么是数据库数据库这个术语的用法很多,一般来说,数据库是一个以某种有组织的方式存储的数据集合。理解数据库的一种最简单的办法是将其想象为一个文件柜。此文件柜是一个存放数据的物理位置,不管数据是什么以及如何组织的。数据库(database):保存有组织的数据的容器......
  • go项目中数据库连接,以及redis连接
     文件:common/mysql.go数据库连接文件packagecommonimport("gorm.io/driver/mysql""gorm.io/gorm""gorm.io/gorm/schema""log")varDB*gorm.DB//全局定义DBvardbErrerror//定义数据库错误funcinit(){dsn:=......
  • 56 openEuler搭建Mariadb数据库服务器-安装、运行和卸载
    56openEuler搭建Mariadb数据库服务器-安装、运行和卸载56.1安装配置本地yum源,详细信息请参考《openEuler22.03-LTS搭建repo服务器》。清除缓存。#dnfcleanall例如示例命令如下:[root@superman-21~]#dnfcleanall45filesremoved[root@superman-21~]#......
  • Shell多线程备份数据库
    Shell这么简单的脚本语言有多线程这一说吗?答案是有的。只不过它实现起来稍微有点难理解罢了,因为它借助了命名管道实现。所谓多线程就是原本由一个进程完成的事情现在由多个线程去完成。假如一个进程需要10小时完成的事情,现在分配10个线程,给他们分工,然后同时去做这件事情,最终可能就......