FYI, MySQL高效分页

2023-05-16

在Percona Performance Conference 2009大会上来自yahoo的Surat Singh Bhati (surat@yahoo-inc.com) 和 Rick James (rjames@yahoo-inc.com)给大家分享了MySQL高效分页的经验。

[size=large][b]一、概述[/b][/size]
[list]
[*]常见分页方式
[*]schema设计和常见的分页方式(偏移)
[*]避免分页偏移过大的技巧
[*]性能对比
[*]重点
[/list]
[size=large][b]二、常见分页方式[/b][/size]
[img]/upload/attachment/122203/d161d9bf-45fc-3f13-acc1-be5eba13bbf2.jpg[/img]
[size=large][b]三.前提[/b][/size]
大记录表要高效分页
[list]
[*]WHERE条件使用索引完成
[*]WHERE条件和排序能够使用同个索引完成
[/list]基础知识[list]
[*][url]http://dev.mysql.com/doc/refman/5.1/en/mysql-indexes.html[/url]
[*][url]http://dev.mysql.com/doc/refman/5.1/en/order-by-optimization.html[/url]
[*][url]http://dev.mysql.com/doc/refman/5.1/en/limit-optimization.html[/url]
[/list]
索引 a_b_c (a, b, c)

下面的查询可以使用索引来解决ORDER部分:
[list]
[*]ORDER BY a
[*]ORDER BY a,b
[*]ORDER BY a, b, c
[*]ORDER BY a DESC, b DESC, c DESC
[/list]
下面的查询可以使用索引来解决WHERE和ORDER部分::
[list]
[*]WHERE a = const ORDER BY b, c
[*]WHERE a = const AND b = const ORDER BY c
[*]WHERE a = const ORDER BY b, c
[*]WHERE a = const AND b > const ORDER BY b, c
[/list]
下面的查询无法使用索引完成,需额外排序:
[list]
[*]ORDER BY a ASC, b DESC, c DESC /* 混合ASC和DESC */
[*]WHERE g = const ORDER BY b, c /* 字段g不是索引一部分 */
[*]WHERE a = const ORDER BY c /* 没有使用字段b */
[*]WHERE a = const ORDER BY a, d /* 字段d不是索引的一部分 */
[/list]
[size=large][b]四、Schema 设计[/b][/size]
CREATE TABLE `message` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`user_id` int(11) NOT NULL,
`content` text COLLATE utf8_unicode_ci NOT NULL,
`create_time` int(11) NOT NULL,
`thumbs_up` int(11) NOT NULL DEFAULT '0', /* 投票数 */
PRIMARY KEY (`id`),
KEY `thumbs_up_key` (`thumbs_up`,`id`)
) ENGINE=InnoDB

mysql> show table status like 'message' \G
Engine: InnoDB
Version: 10
Row_format: Compact
Rows: 50000040 /* 5千万 */
Avg_row_length: 565
Data_length: 28273803264 /* 26 GB */
Index_length: 789577728 /* 753 MB */
Data_free: 6291456
Create_time: 2009-04-20 13:30:45

两个分页例子:[list]
[*]按照time(发布时间)分页,新发布的在前面
[*]按照thumps_up(投票数)分页,票高的在前面
[/list]
[size=large][b]五、典型的分页查询[/b][/size]
[b]1.统计记录数量[/b]
SELECT count(*) FROM message

[b]2. 查询当前页[/b]
SELECT * FROM message ORDER BY id DESC LIMIT 0, 20
[list]
[*]http://domain.com/message?page=1
ORDER BY id DESC LIMIT 0, 20
[*]http://domain.com/message?page=2
ORDER BY id DESC LIMIT 20, 20
[*]http://domain.com/message?page=3
ORDER BY id DESC LIMIT 40, 20
[/list]
提示:id 是自动增长的(auto_increment),通过id就可以取得最新的列表,不需要创建专门记录时间的字段。
[size=large][b]六、explain[/b][/size]
mysql> explain SELECT * FROM message
ORDER BY id DESC
LIMIT 10000, 20\G
***************** 1. row **************
id: 1
select_type: SIMPLE
table: message
type: index
possible_keys: NULL
key: PRIMARY
key_len: 4
ref: NULL
rows: 10020
Extra:
1 row in set (0.00 sec)

[list]
[*]它可以使用索引,并且只要找到需要的结果后就停止扫描.
[*]LIMIT 10000, 20 需要读取前10000行,然后获取后面的20行
[/list]
[size=large][b]六、瓶颈[/b][/size]
[list]
[*]较大的偏移(OFFSET)会增加结果集, MySQL has to bring data in memory that is never returned to caller.
[*]Performance issue is more visible when your have database that can't fit in main memory.
[*]小比例的低效分页足够产生磁盘I/O瓶颈
[*]为了显示“第 21条 至 40条 (共 1000000),需要统计1000000行
[/list]
[size=large][b]七、简单的解决方法[/b][/size]
[list=1]
[*]不显示记录总数,没用户在乎这个数字
[*]不让用户访问页数比较大的记录,重定向他们
[/list]
[size=large][b]八、避免count(*)[/b][/size]
[list=1]
[*]不显示总数,让用户通过“下一页”来翻页
[*]缓存总数,显示一个大概值,没有用户在乎是324533条还是324633 (译:测试在乎-_-!!)
[*]Display 41 to 80 of Thousands
[*]单独统计总数,在插入和删除时递增/递减
[/list]
[size=large][b]九、解决偏移查询[/b][/size]
[list=1]
[*]更改ui,不提供跳到某页的按钮
[*]LIMIT N 是高效的, 但不要使用 LIMIT M,N
[list]
[*]从WHERE条件里找到分页(LIMIT N)的线索
[*]Find the desired records using more restricted WHERE using given clue and ORDER BY and LIMIT N without OFFSET)
[/list]
[/list]
[size=large][b]十、寻找线索[/b][/size]
[img]/upload/attachment/122318/7d3930ab-5602-3245-a651-5f35cf91d1a7.jpg[/img]
译:last_seen是id。这里的分页只有“上一页”、“下一页” 按钮
[size=large][b]十一、根据线索解决方案[/b][/size]
下一页:
http://domain.com/forum?page=2&last_seen=100&dir=next

WHERE id < 100 /* last_seen */
ORDER BY id DESC LIMIT $page_size /* 没有偏移 */

上一页:
http://domain.com/forum?page=1&last_seen=98&dir=prev

WHERE id > 98 /* last_seen */
ORDER BY id ASC LIMIT $page_size /* 没有偏移 */

译:通过每页第一条或最后一条记录的id来做条件筛选,再配合降序和升序获得上/下一页的结果集
[size=large][b]十二、根据线索解决方案[/b][/size]
mysql> explain
SELECT * FROM message
WHERE id < '49999961'
ORDER BY id DESC LIMIT 20 \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: message
type: range
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: NULL
Rows: 25000020 /* 忽略这里 */
Extra: Using where
1 row in set (0.00 sec)

[size=large][b]十三、当你排序的字段不是唯一的,怎么办?[/b][/size]
[quote]99
99
98 第一页
98
98

98
98
97 第二页
97
10[/quote]
我们不能这样查询:
WHERE thumbs_up < 98
ORDER BY thumbs_up DESC /* 结果将返回重复的记录 */

我们可以这样查询:
WHERE thumbs_up <= 98
AND <额外的条件>
ORDER BY thumbs_up DESC

[size=large][b]十四、额外的条件[/b][/size]
[list]
[*]考虑到 thumbs_up 是“主要字段”,如果我们添加一个“次要字段”,我们可以使用“主要字段”和“次要字段”作为查询条件
[*]其次,我们可以考虑使用id(primary key)作为我们的次要字段
[/list]
[size=large][b]十五、解决方案[/b][/size]
第一页:
SELECT thumbs_up, id
FROM message
ORDER BY thumbs_up DESC, id DESC
LIMIT $page_size
+-----------+----+
| thumbs_up | id |
+-----------+----+
| 99 | 14 |
| 99 | 2 |
| 98 | 18 |
| 98 | 15 |
| 98 | 13 |
+-----------+----+


下一页:
SELECT thumbs_up, id
FROM message
WHERE thumbs_up <= 98 AND (id < 13 OR thumbs_up < 98)
ORDER BY thumbs_up DESC, id DESC
LIMIT $page_size
+-----------+----+
| thumbs_up | id |
+-----------+----+
| 98 | 10 |
| 98 | 6 |
| 97 | 17 |

[size=large][b]十六、优化[/b][/size]
查询:
SELECT * FROM message
WHERE thumbs_up <= 98
AND (id < 13 OR thumbs_up < 98)
ORDER BY thumbs_up DESC, id DESC
LIMIT 20

我们可以这样写:
SELECT m2.* FROM message m1, message m2
WHERE m1.id = m2.id
AND m1.thumbs_up <= 98
AND (m1.id < 13 OR m1.thumbs_up < 98)
ORDER BY m1.thumbs_up DESC, m1.id DESC
LIMIT 20;

[size=large][b]十七、explain[/b][/size]
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: m1
type: range
possible_keys: PRIMARY,thumbs_up_key
key: thumbs_up_key /* (thumbs_up,id) */
key_len: 4
ref: NULL
Rows: 25000020 /* 忽略这里 */
Extra: Using where; Using index /* Cover 译:Cover就是说所需要的数据之从索引里获取就可以满足了 */
*************************** 2. row ***************************
id: 1
select_type: SIMPLE
table: m2
type: eq_ref
possible_keys: PRIMARY
key: PRIMARY
key_len: 4
ref: forum.m1.id
rows: 1
Extra:

[size=large][b]十八、性能提升[/b][/size]
[img]/upload/attachment/124012/447f5046-3d39-3a9d-8f7c-72f123e9a8d4.jpg[/img]

[img]/upload/attachment/124010/f4569ac9-cada-386f-83e8-6b4cbdd57a22.jpg[/img]
[size=large][b]十九、吞吐量提升[/b][/size]
每页30条记录,查看第一页的话,使用 LIMIT OFFSET, N方式,可以达到 600 次查询/秒,如果使用 LIMIT N (无偏移)方式,提升到 3.7k 次查询/秒
[size=large][b]二十、Bonus Point[/b][/size]
Product issue with LIMIT M, N

User is reading a page, in the mean time some records may be added to
previous page.

Due to insert/delete pages records are going to move forward/backward
as rolling window:
– User is reading messages on 4th page
– While he was reading, one new message posted (it would be there on page
one), all pages are going to move one message to next page.
– User Clicks on Page 5
– One message from page got pushed forward on page 5, user has to read it
again

No such issue with news approach
[size=large][b]二十一、不足[/b][/size]
SEO专家会说:Let bot reach all you pages with fewer number of deep dive

[b]两个解决方案:[/b]
Two Solutions:
• Read extra rows
– Read extra rows in advance and construct links for few previous & next pages
• Use small offset
– Do not read extra rows in advance, just add links for few past & next pages
with required offset & last_seen_id on current page
– Do query using new approach with small offset to display desired page
[img]http://dl.iteye.com/upload/attachment/163016/26f3f5f8-11eb-3bf3-95b5-34fe6fd1c63b.png[/img]

Additional concern: Dynamic urls, last_seen is not constant over time.

原文:[url=/admin/blogs/]http://www.percona.com/ppc2009/PPC2009_mysql_pagination.pdf[/url]
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

FYI, MySQL高效分页 的相关文章

  • MySQL - 通过部分单词匹配和相关性评分进行高效搜索(全文)

    如何进行 MySQL 搜索 既匹配部分单词 又提供准确的相关性排序 SELECT name MATCH name AGAINST math IN BOOLEAN MODE AS relevance FROM subjects WHERE M
  • PHP 7.0和MySQL启动错误“未定义符号:mysqlnd_allocator in Unknown”

    即使在运行时 在自定义编译版本的 PHP7 上也会收到此警告php v 尝试了发布的所有解决方案 什么可能导致这种情况 PHP 警告 PHP 启动 无法加载动态库 usr lib php 20151012 pdo mysql so usr
  • 为什么有时自增列的值会有一个或多个间隙?

    我有一个这样的表 colors id color 1 red 2 blue id column is auto increment PK 当我向该表中插入一些新值时 有时会出现一些间隙id柱子 像这样的事情 INSERT INTO colo
  • 开发 WordPress 管理链接重定向到实时站点

    我正在尝试对我拥有的 WordPress 网站进行新的更改 所以我复制了所有文件并导出到新的开发子域 为子域创建新数据库并从实时站点导入数据库 直播站点 http mysite com http mysite com 开发站点 http d
  • 当子查询具有组列时,MySQL 8 不使用 INDEX

    我们刚刚从 mariadb 5 5 迁移到 MySQL 8 一些更新查询突然变得很慢 经过更多调查 我们发现当子查询有组列时 MySQL 8不使用索引 例如 下面是一个示例数据库 桌子users维护每种类型用户的当前余额 表 帐户 维护每天
  • Session_set_save_handler 未设置

    我在设置 session set save handler 时遇到问题 我将 php ini 配置为 session handler user 这个简单的测试失败了 Define custom session handler if sess
  • 如何优化这个查询(涉及4毫米表)

    我正在使用如下所示的遗留数据库架构 product table表有字段 uid 整数 主键 name varchar 50 category表有字段 uid 整数 主键 name varchar 50 好吧 现在product table与
  • UTF-8、PHP 和 XML Mysql

    我在解决这个问题时遇到了很大的问题 我有一个编码 latin1 swedish ci 的 mysql 数据库和一个存储名称和地址的表 我正在尝试输出 UTF 8 XML 文件 但在使用以下字符串时遇到问题 Otiv gen它被输出为Otiv
  • mysql 将 varchar 字段排序为整数

    我的表中有一个 varchar 字段 我想对其进行排序 但我需要将此字段作为整数处理 意思是如果按文本排序 顺序是 19 2 20 但我想得到正确的顺序 2 19 20 谁能帮我 我不知何故没有设法运行查询CAST 我总是得到Error C
  • 删除除一行之外的所有具有重复值的行

    我有一个包含三列的表 KEY VALUE and LAST UPDATED 有重复的VALUE字段 我想删除所有具有相同的行VALUE和其他人一样except获取最新更新的信息 因此 如果表包含这些行 1 A 2013 11 08 2 B
  • mysql - 选择日期时间和组中的小时

    我有一个 ShoppingDates 的日期时间列 假设我有 1000 行 2012 年 7 月 18 日 5 33 39 下午 2012 年 7 月 16 日 6 64 39 下午 2012 年 7 月 14 日 7 34 39 下午 2
  • SELECT COUNT() 与 mysql_num_rows();

    我有一个大表 60 数百万条记录 我正在使用 PHP 脚本来浏览该表 PHP 脚本 带分页 加载速度非常快 因为 表引擎是InnoDB因此SELECT COUNT 非常慢并且mysql num rows 不是一个选项 所以我将总行数 我用来
  • Mysql使用触发器建表

    我尝试在 Mysql 触发器内创建表 但没有创建 如何使用触发器创建表 这里传递的表的名称是动态的 据我所知 在触发器内创建表是不可能的 看这里 http forums mysql com read php 99 121849 122609
  • Java 日期和 MySQL 时间戳时区

    我正在编辑一段代码 其基本功能是 timestamp new Date 然后坚持下去timestamp中的变量TIMESTAMPMySQL 表列 然而 通过调试我看到Date显示在正确时区的对象 GMT 1 当持久化在数据库上时 它是GMT
  • MySQL 错误 1264:列的值超出范围

    As I SETMySQL 中的 cust fax 表如下所示 cust fax integer 10 NOT NULL 然后我插入这样的值 INSERT INTO database values 3172978990 但随后它说 错误 1
  • 我无法访问 XAMPP phpMyAdmin;它说:错误 MySQL 说:文档无法连接:无效设置

    完整错误消息 Error MySQL said Documentation Cannot connect invalid settings Connection for controluser as defined in your conf
  • 存储 MySQL GUID/UUID

    这是我能想到的将 UUID 生成的 MySQL GUID UUID 转换为二进制文件 16 的最佳方法 UNHEX REPLACE UUID 然后将其存储在 BINARY 16 中 我应该知道这样做有什么影响吗 从 MySQL 8 0 及以
  • 更改 MySQL Workbench 上的默认字符集

    我正在尝试使用连接到我的 MYSQL 数据库utf8mb4字符集 请注意 数据库字符集的全局设置已经是 utf8mb4 我可以使用 CLI 轻松完成此操作 如下所示 mysql h myhostname u myuser p default
  • Join 表(关联表)有主键吗?多对多关系

    Join 表 关联表 有主键吗 多对多的关系 我见过一些带有主键的连接表 一些没有 有人可以解释一下连接表中何时会有主键吗 为什么 先感谢您 在纯 联接 或联结表中 所有字段都将成为主键的一部分 例如 让我们考虑下表 CREATE TABL
  • 计算 MySQL 中的行数以及实际行内容

    MySQL 中有没有办法执行单个 SQL 语句来返回所选行以及结果行数 我可以做这个 SELECT COUNT FROM BigTable WHERE firstname LIKE a 这给了我一个带有计数 37 781 的结果行 我可以像

随机推荐

  • Python小技巧之——巧用with语句实现异常处理

    Python的异常处理语句try except大家都很熟悉了 xff0c 例如 xff1a try 1 0 except Exception as ex print ex integer division or modulo by zero
  • 将lwip1.4.1工程移植至lwip2.1.2记录

    将lwip1 4 1工程移植至lwip2 1 2记录 1 ip addr结构体2 etharp h3 cc h与arch h4 tcp impl h 关于二者文件 功能等差异网上已有很多文章介绍 xff0c 类似这个文档有简单说了下这两个版
  • 在Windows和Ubuntu上安装VNC连接远程服务器

    如果你是公用的服务器的管理员需要添加一些用户 xff0c 并配置VNC服务以便远程访问 xff0c 简要介绍一下配置方法 具体的命令可以参照命令手册去查看 man span class hljs command span class hlj
  • 变频器的工作原理及其电路分析

    变频器简单的说就是结合了变频技术和微电子技术研制出来的可以改变输入电源的频率得到另外一种频率电源输出的设备 其输入的电源就是我们工业上面使用的电源 xff0c 一般都是电压和频率都固定不变的交流电 240v或者380v交流电 通过内置的一些
  • 欠拟合、过拟合及其解决方法

    在我们机器学习或者训练深度神经网络的时候经常会出现欠拟合和过拟合这两个问题 xff0c 但是 xff0c 一开始我们的模型往往是欠拟合的 xff0c 也正是因为如此才有了优化的空间 xff0c 我们需要不断的调整算法来使得模型的表达能拿更强
  • ubuntu18.04安装ROS Melodic的详细过程以及填坑经历

    一 版本说明 ROS官方将在2021年不再维护Kinetic xff0c 后续使用Ubuntu18 04 43 Melodic组合 xff0c Melodic支持时间到2023年5月 二 安装前Ubuntu18 04设置 打开Ubuntu1
  • win10和ubuntu20双系统设置默认启动系统为win10

    在win10下安装了Ubuntu20 04系统 xff0c 默认情况下 xff0c 启动的是Ubuntu系统 要将默认启动系统设置成win10 xff0c 方法如下 xff1a 1 进入ubuntu系统 xff0c 按住Ctrl 43 Al
  • Keil添加芯片支持包(Pack)

    1 前言 一直用STM32的芯片 xff0c 现在想看看工程是否可以在其他厂家的芯片上跑 xff0c 可是keil的Device中只有ST厂家的 因此 xff0c 尝试在keil中添加其他厂家的芯片支持包 2 keil软件内安装 点击工具栏
  • Qt 设置窗体大小和背景颜色

    1 一种方法是设置它的最大窗口值和最小窗口值 xff0c 并且使最大值和最小值相等 简单的示例 xff1a setMinimumSize 370 150 setMaximumSize 370 150 此时窗口大小便被固定为 xff08 37
  • Shell 脚本详解

    简介 shell xff1a 蛋 壳 shell脚本是在操作系统外 xff0c 可以直接调用系统内核命令的一个脚本语言 shell脚本可以分为两大类组成 xff1a 1 命令行 xff08 系统命令行 xff09 2 脚本语法 xff08
  • Windows——电脑不能连接手机热点(WLAN显示已经禁用)的解决办法

    笔记本电脑提示 xff1a 已关闭无线功能 基于这篇博客之上 xff0c 在第二步中 xff0c 关闭WLAN AutoConfig 服务 xff0c 之后重新打开WLAN AutoConfig 服务 xff0c 即可
  • Ubuntu——系统语言由英文切换到中文的方法

    一 方法一 ubuntu设置系统语言为中文 二 方法二 若方法一中不能拖动中文输入法到第一行 xff0c 则可以直接采取卸载英文输入法 xff0c 这样就中文输入法到第一行了 xff0c 切换成中文了 英文输入法可以根据需要考虑是否安装 一
  • RealSense D435——基本介绍

    一 结构介绍 采用的是结构光Tof成像方案 正面的四个摄像头从左至右 xff0c 依次是左红外相机 红外点阵投影仪 右红外相机 RGB相机 xff08 前三个负责形成深度图 xff0c 最后一个就形成RGB图 xff09 二 小贴士 RGB
  • RealSense D435——相机内参获取

    RealSense D435 相机内参获取 一 参考博客二 小贴士2 1 遇到的问题及解决方案问题一描述问题一解决方法问题二描述问题二解决方法 一 参考博客 RealSense D435内参获取环境配置 xff1a Realsense D4
  • Vscode——报错解决:Unable to start debugging.Unexpected GDB output from command. 或 程序点击运行一直无结果

    一 报错截图 1 Unable to start debugging Unexpected GDB output from command 2 程序点击运行一直无结果 二 原因 路径中含有中文 三 解决办法 将文件放入不包含中文的路径下
  • Github——合并分支

    一 当两个分支不一样时 xff0c 会出现下面的标志 xff08 前提是设定了分支保护 xff09 xff0c 点击Compare amp pull request 二 选择双方分支 三 处理请求 四 确认请求
  • 基于四旋翼飞行器的陀螺仪、加速度计、磁力计传感器说明

    一 什么是磁力计 加速度计和陀螺仪以及他们之间的区别 1 什么是陀螺仪 加速度计和磁力计 xff1f xff08 1 xff09 陀螺仪 xff08 Gyroscope GYRO Sensor xff09 也叫地感器 xff0c 三轴陀螺仪
  • 操作系统(二) -- 操作系统的接口与实现

    前言操作系统的接口 什么是操作系统的接口POSIX标准 系统调用的实现 1 xff0c 用户程序能不能直接调用系统内核2 xff0c 如果不能直接调用 xff0c 为什么 xff1f 如何实现的3 xff0c 用户程序如何才能调用系统内核系
  • 自动驾驶路径规划技术-高速公路路径规划

    Path Planning Highway Driving project Github https github com williamhyin CarND Path Planning Email williamhyin 64 outlo
  • FYI, MySQL高效分页

    在Percona Performance Conference 2009大会上来自yahoo的Surat Singh Bhati surat 64 yahoo inc com 和 Rick James rjames 64 yahoo inc