首页 > 数据库 >MySQL大表分页查询的坑以及解决方案

MySQL大表分页查询的坑以及解决方案

时间:2024-01-31 16:14:40浏览次数:35  
标签:分页 name 解决方案 OFFSET 查询 LIMIT MySQL uid 大表

最近在做一个需求,需求内容中有一个功能点是查询指定用户标签里的用户id,这里做了分页查询,分页查询是用mysql的LIMIT设置offset和size值来实现的。在程序执行过程中会发现,如果查询的用户标签数据量很大时会出现慢查询告警,这里已经对mysql表的标签名称和用户id字段都加了索引,并且limit的数量也不大,为什么还会出现慢查询呢?经过排查,发现了问题主要出现在分页查询的Limit语句上。

慢查询语句:

SELECT * FROM user_cluster_name WHERE cluster_name = "test" LIMIT 1100000,1000

慢查询的原因:

limit会扫描全部索引,偏移量越大,速度越慢。

解决办法:

查询时按用户id排序,用uid做游标,查询下一页时,传入上一页最后一个id,不再扫描已经查询过的数据。优化后的sql语句如下:

SELECT * FROM user_cluster_name WHERE cluster_name = "test" and uid > 12345  ORDER BY uid ASC LIMIT 0,1000 

查找出uid大于12345之后的1000个uid。因为我们已经对cluster_name字段和uid字段都加了索引,上面的语句走了索引分页查询也不需要每次都从第一个数据开始扫描,直接从uid>xx的字段开始扫描取出数据,在大表的场景下这种优化的效果会非常显著。

附上Golang代码:

func (m *Client) BatchGetUserCluster(
	ctx context.Context,
	clusterName string,
	bizType BizType,
	cursor int64,
	size int,
) (
	items []*UserCluster,
	isEnd bool,
	nextCursor int64,
	err error,
) {
	items = make([]*UserCluster, 0)
	query := m.Client.WithContext(ctx).Table(UserClusterTable).Where("cluster_name=? AND biz_type=?",
		clusterName, bizType)
	if cursor > 0 {
		query = query.Where("uid>?", to.Int64(cursor))
	}

	err = query.Order("uid ASC").Limit(size).Find(&items).Error
	if err != nil {
		logrus.Errorf("query rec list failed . %s", err)
		return
	}
	lenItems := len(items)
	if lenItems < size {
		isEnd = true
	} else if lenItems > 0 {
		nextCursor = items[lenItems-1].UID
	}
	return
}

服务代码优化后重新发布,慢查询告警再也没出现过了。sql语句执行时间也降到了正常值范围内,慢查询问题算是解决了。

思考:

从上面的结果来看,分页用游标比用offset偏移量好用,为什么mysql还会有offset偏移量做分页的功能呢?

在MySQL中,使用LIMITOFFSET进行分页查询确实可能导致偏移量越大时查询速度越慢,这是因为随着偏移量的增加,数据库需要扫描的数据量也相应地增加,从而导致查询性能下降。

尽管存在这种性能问题,LIMITOFFSET仍然是非常有用的分页机制,原因如下:

  1. 简单性:使用LIMITOFFSET提供了一种简单、直观的方式来分页。你只需要指定每页的大小(LIMIT)和要跳过的记录数(OFFSET),然后数据库会为你处理剩下的部分。
  2. 灵活性:你可以根据需要随时更改每页的大小或跳过的记录数,而无需更改查询的结构或访问数据的方式。
  3. 与SQL标准兼容LIMITOFFSET是许多SQL方言中常见的分页方法,因此使用它们可以帮助确保你的代码具有更好的可移植性。
  4. 与程序语言集成:许多编程语言和框架都提供了对LIMITOFFSET的支持,这使得在应用程序中实现分页变得相对容易。
  5. 易于缓存:如果你对结果进行了适当的缓存,并且使用了诸如Memcached或Redis之类的缓存解决方案,那么LIMITOFFSET可以帮助你轻松地处理缓存失效的情况。

尽管LIMITOFFSET有其用途,但在处理大量数据时,它们可能不是最高效的分页机制。对于大型数据集,可能需要考虑其他分页策略,例如基于游标、基于位置或基于哈希的分页方法。这些方法通常更适用于大数据场景,并能够提供更好的性能。

标签:分页,name,解决方案,OFFSET,查询,LIMIT,MySQL,uid,大表
From: https://www.cnblogs.com/wuyepeng/p/17999475

相关文章

  • 数据库研发人员必看的MySQL 8.0新特性
    本文汇总了MySQL8.0面向开发的新特性,总共有12个新特性,有想快速了解8.0新特性的朋友,可以看一下哈文章目录:1.公用表达式支持-CTE2.窗口函数3.表达式作为默认值:4.CHECK支持5.隐藏列-MySQL8.0.236.隐藏索引、降序索引、函数索引(MySQL-8.0.19)7.VALUES语法8.INTERSECT和EXC......
  • mysql导入数据的问题
    简介:在我们使用高版本mysql的时候,比如mysql8,到mysql5.7恢复就报错,比如下面这样的错误:在我们使用高版本mysql的时候,比如mysql8,到mysql5.7恢复就报错,比如下面这样的错误:[Err]1273-Unknowncollation:'utf8mb4_0900_ai_ci'这就是字符集编码的问题,但是我将数据库的编码改成了......
  • Prometheus+Grafana+Jmeter监控服务器资源及中间件(Prometheus & node_exporter &mysq
    一、Prometheus&node_exporter&Grafana的原理Prometheus:Prometheus是一个开源的系统监控和报警工具包,它负责定时从各种数据源(如NodeExporter)中获取指标数据,并将其存储在自己的时间序列数据库中。Prometheus支持灵活的查询和报警功能,用户可以方便地对这些指标数据进行查询......
  • mysql找出不包含某些关键字的结果
    比如公司业务是和房产相关的,但是库里存在和房产不相关的内容时就需要筛选并删除。如何筛选才能不误伤呢?这是我的一个初步的SQL: 意思是如果name、desc字段都不包含房、盘、楼、地产关键字才找出来。如下: 这篇文章就到这里啦!如果你对文章内容有疑问或想要深入讨论,欢迎......
  • Prometheus结合Consul采集多个MySQL实例的监控指标
    本文主要介绍如何利用Prometheus官网提供的mysqld_exporter进行多MySQL实例的监控指标采集建议安装最新版的mysqld_exporter,因为从’2022-09-01’之后才支持多实例功能的。具体的官网说明详见 Supportforscrapingmultiplemysqldhosts(#651)[1]ok,开始今天的教程~1、下载安......
  • MySQL Shell 8.0.32 for GreatSQL编译二进制包
    MySQLShell8.0.32forGreatSQL编译二进制包构建MySQLShell8.0.32forGreatSQL0.写在前面之前已经写过一篇前传MySQLShell8.0.32forGreatSQL编译安装,最近再次编译MySQLShell二进制包时,发现了一些新问题,因此重新整理更新本文档。1.几处新问题这次编译MySQLShe......
  • MySQL 中的 distinct 和 group by 哪个效率更高?
    先说大致的结论(完整结论在文末):在语义相同,有索引的情况下:groupby和distinct都能使用索引,效率相同。在语义相同,无索引的情况下:distinct效率高于groupby。原因是distinct和 groupby都会进行分组操作,但groupby可能会进行排序,触发filesort,导致sql执行效率低下。基于这个......
  • MySQL常用函数
    一)字符函数①length(str)函数获取参数值的字节个数对于utf-8字符集来说,一个英文占1个字节;一个中文占3个字节;对于gbk字符集来说,一个英文占1个字节;一个中文占2个字节;演示如下:selectlength('关注作者')as长度fromdual; ②concat(str1,str2,...)函数将字符串拼接,通过输入......
  • mysql 创建数据库、创建用户
    --设置密码SHOWVARIABLESLIKE'validate_password%';setglobalvalidate_password_check_user_name='ON';setglobalvalidate_password_policy='LOW';setglobalvalidate_password_length=6; --创建数据库createdatabaseifnotex......
  • 面试官:请说一下Mysql中count(1)、count(*)以及count(列)的区别?
    近期在Review项目代码时,发现同事们在查询MySQL行数时存在多样的方式,有的使用COUNT(1),有的用COUNT(id),还有人选择了COUNT(*)。这混杂的选择引发了我的思考。当然这三种count的方式也是众说纷纭,其中最大的分歧点就是COUNT(*)和COUNT(1)查询性能上,有人觉得COUNT(*)需要转换为COUN......