在具有10百万行的表上使用1个联接优化查询

如何解决在具有10百万行的表上使用1个联接优化查询

我正在考虑使用2个表更快地发出请求。
我有以下两个表:

表格“日志”

  • id varchar(36) PK
  • date timestamp(2)
  • 更多varchar字段和一个文本字段

该表具有PHP Laravel框架所称的与其他几个对象的“多态多对多”关系,因此有第二个表“ logs_pivot”:

  • id unsigned int PK
  • log_id varchar(36) FOREIGN KEY (logs.id)
  • model_id varchar(40)
  • model_type varchar(50)

logs_pivot中每个条目在logs中有一个或几个条目。它们分别具有20+和10+百万行。

我们这样查询:

select * from logs 
join logs_pivot on logs.id = logs_pivot.log_id
where model_id = 'some_id' and model_type = 'My\Class'
order by date desc
limit 50;

很显然,我们在model_id和model_type字段上都有一个复合索引,但是请求仍然很慢:每次都要几(数十秒)秒。
我们在date字段上也有一个索引,但是EXPLAIN表明这是使用的model_id_model_type索引。

说明:

+----+-------------+-------------+------------+--------+--------------------------------------------------------------------------------+-----------------------------------------------+---------+-------------------------------------------+------+----------+---------------------------------+
| id | select_type | table       | partitions | type   | possible_keys                                                                  | key                                           | key_len | ref                                       | rows | filtered | Extra                           |
+----+-------------+-------------+------------+--------+--------------------------------------------------------------------------------+-----------------------------------------------+---------+-------------------------------------------+------+----------+---------------------------------+
|  1 | SIMPLE      | logs_pivot  | NULL       | ref    | logs_pivot_model_id_model_type_index,logs_pivot_log_id_index | logs_pivot_model_id_model_type_index | 364     | const,const                               |    1 |   100.00 | Using temporary; Using filesort |
|  1 | SIMPLE      | logs        | NULL       | eq_ref | PRIMARY                                                                        | PRIMARY                                       | 146     | the_db_name.logs_pivot.log_id |    1 |   100.00 | NULL                            |
+----+-------------+-------------+------------+--------+--------------------------------------------------------------------------------+-----------------------------------------------+---------+-------------------------------------------+------+----------+---------------------------------+

在其他表中,通过在索引中包含日期字段,我能够更快地发出类似的请求。但是在那种情况下,它们在单独的表中。

当我们要访问这些数据时,它们通常需要几个小时/天。
我们的InnoDB池太小,无法将所有数据(以及所有其他表)保存在内存中,因此很可能总是在磁盘上查询数据。

我们可以怎样更快地提出请求?
理想情况下,仅使用另一个索引或通过更改索引的方式。

非常感谢!


编辑17h05:
到目前为止,谢谢大家的答复,我将尝试O Jones的建议,并以某种方式在数据透视表中包括日期字段,以便可以将其包括在索引索引中。


编辑14/10 10小时。

解决方案:

因此,我最终通过对数据透视表的id字段进行了排序来改变了请求的执行方式,确实允许将其放入索引中。

当未按日期过滤时,对总行数进行计数的请求也更改为仅在数据透视表上完成。

谢谢大家!

解决方法

我看到两个问题:

  • 当表相对于RAM大小巨大时,UUID成本很高。

  • LIMIT 不能得到最佳处理,因为WHERE子句来自一个表,而ORDER BY列来自另一个表。也就是说,它将处理所有JOIN,然后进行排序并最终剥离几行。

,

只是一个建议。使用复合索引显然是一件好事。另一个可能是按日期对ID进行资格预审,并根据您的logs_pivot表索引(model_id,model_type,log_id)来扩展索引。

如果您要查询数据,并且整个历史记录为20+百万条记录,那么数据仅处理每个给定类别的模型ID /类型最多只能有50条记录的数据才走多远。说三个月? vs说您5年的日志? (未在帖子中列出,仅作为示例)。因此,如果您可以查询日期大于3个月的最小日志ID,则该ID可以限制logs_pivot表中发生的其他事情。

类似

select
      lp.*,l.date
   from
      logs_pivot lp
         JOIN Logs l
            on lp.log_id = l.id
   where
          model_id = 'some_id' 
      and model_type = 'My\Class'
      and log_id >= ( select min( id )
                         from logs
                        where date >= datesub( curdate(),interval 3 month ))
   order by 
      l.date desc
   limit  
      50;

因此,log_id的where子句执行一次,仅返回3个月之前的ID,而不返回logs_pivot的整个历史记录。然后,您可以使用经过优化的由两部分组成的模型ID /类型的键进行查询,但是还可以跳至索引的末尾,索引键中包含的ID可以跳过所有历史记录。

您可能要包括的另一件事是一些预先汇总的表,这些表汇总了多少记录,例如每个给定模型类型/ id的每月/每年。将其用作向用户展示的预查询,然后可以将其用作下钻以进一步获得更多详细信息。预汇总表可以在所有历史资料上完成一次,因为它是静态的并且不会改变。您唯一需要不断更新的就是当前每个月的时间段,例如每晚。甚至可能更好,通过触发器,触发器每次添加一次插入记录,或者根据年/月汇总更新给定模型/类型的计数。再次,这只是一个建议,没有其他关于如何/为什么将数据呈现给最终用户的上下文。

,

javascript是一个臭名昭著的查询性能反模式。为什么?服务器将一整排长行排序,然后丢弃几乎所有长行。您的SELECT columns FROM big table ORDER BY something LIMIT small number之一是LOB(即TEXT列)并没有帮助。

这是一种可以减少开销的方法:通过找到所需的主键集找出所需的行,然后仅获取那些行的内容。

您想要什么行?此子查询找到它们。

columns

这可以很轻松地计算出所需的行。因此,这是您需要优化的查询。

可以通过 SELECT id FROM logs JOIN logs_pivot ON logs.id = logs_pivot.log_id WHERE logs_pivot.model_id = 'some_id' AND logs_pivot.model_type = 'My\Class' ORDER BY logs.date DESC LIMIT 50 上的该索引来加速

logs

CREATE INDEX logs_date_desc ON logs (date DESC); 上的此三列复合索引

logs_pivot

此索引可能会更好,因为优化器将在CREATE INDEX logs_pivot_lookup ON logs_pivot (model_id,model_type,log_id); 上看到过滤,但在logs_pivot上看不到过滤。因此,它将首先在logs中查找。

或者也许

logs_pivot

尝试一个然后另一个,以查看产生更快结果的方法。 (我不确定JOIN将如何使用复合索引。)(或简单地将两者都添加,然后使用CREATE INDEX logs_pivot_lookup ON logs_pivot (log_id,model_id,model_type); 来查看使用的是哪个索引。)

然后,当您对子查询的性能感到满意(或无论如何满意)时,可以使用它来获取所需的行,像这样

EXPLAIN

这行得通,因为它排序的数据较少。 SELECT * FROM logs WHERE id IN ( SELECT id FROM logs JOIN logs_pivot ON logs.id = logs_pivot.log_id WHERE logs_pivot.model_id = 'some_id' AND model_type = 'My\Class' ORDER BY logs.date DESC LIMIT 50 ) ORDER BY date DESC 涵盖三列的索引也有帮助。

请注意,子查询和主查询都具有ORDER BY子句,以确保返回的详细结果集符合您所需的顺序。

编辑 Darnit,曾经使用过MariaDB 10+和MySQL 8+,所以我忘记了旧的限制。试试这个吧。

logs_pivot

最后,如果您知道只关心比某些时间新的行,则可以在子查询中添加类似的内容。

SELECT * 
  FROM logs
  JOIN (
                  SELECT id
                    FROM logs
                    JOIN logs_pivot 
                            ON logs.id = logs_pivot.log_id
                   WHERE logs_pivot.model_id = 'some_id'
                     AND model_type = 'My\Class'
                   ORDER BY logs.date DESC
                   LIMIT 50
        ) id_set ON logs.id = id_set.id
  ORDER BY date DESC

如果表中有大量历史数据,这将有很大帮助。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 <select id="xxx"> SELECT di.id, di.name, di.work_type, di.updated... <where> <if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 <property name="dynamic.classpath" value="tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams['font.sans-serif'] = ['SimHei'] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -> systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping("/hires") public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate<String
使用vite构建项目报错 C:\Users\ychen\work>npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-