limit 100w,10为什么查询慢?如何优化?

前言

在 mysql数据库 中,limit x,y 的查询中,x 值越大,那么查询速度也就越慢,例如以下示例:

  • limit 0,10:我们知道,这个查询时间很快,在20毫秒左右。
  • limit 1000000,10:查询时间可能是 25 秒左右(1秒=1000 毫秒),甚至更长时间。

通过这里可以看出,limit 中 x值越大,那么查询速度就越慢。

查询慢原因

这个问题其实就是 MySQL 中典型的深度分页问题。这主要是因为数据库需要扫描和跳过 x 条记录才能返回 y条结果。随着 x的增加,需要扫描和跳过的记录数也增加,从而导致性能下降。

这是因为LIMIT语句仅仅限制了结果集的返回数量,但数据库仍然需要执行全量查询,然后丢弃超出限制数量的记录。 

优化方法有以下两种:

01、起始ID定位

起始 ID 定位指的是 limit 查询时,指定起始 ID。而这个起始 ID 是上一次查询的最后一条 ID。例如上一次查询的最后一条数据的 ID 为 1000000,那我们就从 1000001 开始扫描表,直接跳过前面的 1000000 条数据,这样查询的效率就高了,具体实现 SQL 如下:

select name, age, gender from person where id > 1000000 order by id limit 10;

其中 id 字段为表的主键字段。

为什么起始ID查询效率高呢?

因此这种查询是以上一次查询的最后 ID 作为起始 ID 进行查询的,而上次的 ID 已经定位到具体的位置了,所以只需要遍历 B+ 树叶子节点的双向链表(主键索引的底层数据结构)就可以查询到后面的数据了,所以查询效率就比较高。

这种查询方式,只适合一页一页的数据查询,但如果用户是跳着分页的,例如查询完第 1 页之后,直接查询第 120 页,那么这种实现方式就不行了。

02、索引覆盖+子查询

未优化前的sql如下:

select name, age, gender
from person
order by createtime desc 
limit 1000000,10;

在以上sql中,createtime 字段创建了索引,但查询效率依然很慢,因为它要取出 100w 完整的数据,并需要读取大量的索引页,和进行频繁的回表查询,所以执行效率会很低。

对于上面sql,我们可以做以下优化:

SELECT p1.name, p1.age, p1.gender
FROM person p1
JOIN (
    SELECT id FROM person ORDER BY createtime desc LIMIT 1000000, 10
) AS p2 ON p1.id = p2.id;

相比于优化前的sql,优化后的 SQL 将不需要频繁回表查询了,因为子查询中只查询主键 ID,这时可以使用索引覆盖来实现。那么子查询就可以先查询出一小部分主键 ID,再进行查询,这样就可以大大提升查询的效率了。 

索引覆盖(Index Coverage)是一种数据库查询优化技术,它指的是在执行查询时,数据库引擎可以直接从索引中获取所有需要的数据,而不需要再回表(访问主键索引或者表中的实际数据行)来获取额外的信息。这种方式可以减少磁盘 I/O 操作,从而提高查询性能。

当使用LIMIT子句获取大量数据时,我们可以考虑以下优化方案:

使用缓存

考虑将结果缓存到缓存系统中,以便在后续请求时候直接从缓存中获取数据,而不需要重新查询数据库。

优化查询语句

分析查询语句,确保它使用了合适的索引和优化技术。避免不必要的计算和连接操作,以减小查询的复杂度。

异步处理

如果业务允许,可以考虑将查询任务异步化,将查询结果存储到临时表中,然后异步加载到应用层。

数据库优化

考虑对数据库服务器进行性能优化,包括优化配置、增加硬件资源、调整缓冲池大小等。考虑使用数据库分片、分表等技术以提高查询性能。

分页查询

将大的结果集分割成多个小的结果集,每次只加载一页数据。使用LIMIT和OFFSET子句进行分页,例如,每次查询1000条记录。
select * from table_name limit 1000 offset 0; --第一页
select * from table_name limit 1000 offset 1000; --第二页
注意,随着OFFSET值的增加,查询性能可能逐渐降低,因为数据库需要跳过越来越多的记录。

<think>首先,用户查询是:“我想查找对5000w数据查询电话号码后4位进行优化的方法”,意思是“我想查找优化5000w数据中查询电话号码后4位的方法”。 用户提供了几个引用: - 引用[1]:讨论MySQL查询优化,使用子查询形式如 `SELECT * FROM sys_person WHERE id >= (SELECT id FROM sys_person ORDER BY id LIMIT 9000000,1) LIMIT 0,100;` 来高效查询。 - 引用[2]:提到模糊查询,如 `SELECT * FROM order_info WHERE 姓名索引 like '%5wk1O4m%'`,但这是针对精确查询的。 - 引用[3]:提供SQL优化技巧,如使用LIMIT、避免SELECT *、索引使用等。 - 引用[4]:讨论数据查询策略,支持多种查询语言如SQL、Solr、GraphQL、SPARQL。 用户真正的需求是优化查询电话号码后4位(例如,查询电话号码以特定后缀结尾的记录)。电话号码后4位可能存储在数据库中,比如一个phone_number字段。 关键点: - 数据量:5000万行。 - 查询:基于电话号码的后4位,这类似于后缀查询。 在SQL中,后缀查询(如WHERE phone_number LIKE '%1234')通常效率低下,因为索引不能很好地支持后缀匹配(索引是从左到右匹配的)。 我需要参考站内引用来提供优化方案。 从引用[3]中,有许多优化建议: - 避免使用LIKE '%pattern',因为它会导致全表扫描。 - 使用索引:但后缀查询不能直接利用索引,除非使用反转字符串等技术。 - 其他技巧:如使用LIMIT分页、避免SELECT *等。 引用[1]提到了使用子查询LIMIT来高效分页,这适用于大表查询,但不直接针对后缀查询。 引用[4]提到了支持多种查询语言,如Solr或GraphQL,意思是使用全文搜索引擎或优化查询语言。 对于后缀查询优化,常见方法包括: 1. **索引优化**:如果数据库支持,可以创建反转索引(reverse index)。例如,在phone_number上创建一个新列,存储电话号码的反转字符串,然后在反转字符串上创建索引。查询时,反转查询后缀后再匹配前缀。 2. **使用数据库特定功能**:如MySQL的函数索引或虚拟列(适用于某些版本)。 3. **分页和LIMIT**:如引用[1]所示,避免一次性载所有数据。 4. **避免全表扫描**:强迫使用索引或优化查询语句。 5. **考虑外部工具**:如使用Elasticsearch或Solr进行全文搜索,它们支持后缀匹配。 6. **数据预处理**:存储后4位作为单独列,并索引该列。 用户的需求是“查询电话号码后4位”,意思是基于电话号码的后缀查询。 结构化我的回答: - **理解问题**:解释后缀查询的挑战。 - **优化方法**:基于引用提供方案。 - **具体示例**:给出SQL示例。 - **生成相关问题**:作为系统指令的一部分。 确保格式正确: - 行内数学使用$...$。 - 独立公式使用$$...$$(但这里可能不需要)。 - 使用中文。 - 引用的末尾添引用标识,如[^1]。 引用用户提供的引用: - [^1]:来自引用[1],关于高效LIMIT查询。 - [^2]:来自引用[2],关于模糊查询,但注意后缀查询类似。 - [^3]:来自引用[3],有多个优化点。 - [^4]:来自引用[4],关于查询策略。 针对后缀查询的具体优化: 1. **存储后4位作为单独列**:添一个新列last_four_digits,存储电话号码的后4位。然后在这个列上创建索引。查询时使用精确匹配:SELECT * FROM table WHERE last_four_digits = '1234'。这是最有效的,因为索引支持等值查询。 2. **使用反转字符串**:创建新列reversed_phone,存储电话号码的反转字符串。创建索引。查询时,反转查询后缀:SELECT * FROM table WHERE reversed_phone LIKE '4321%' (因为反转后,后缀变成前缀)。 3. **数据库特定特性**:例如,MySQL 5.7+支持虚拟列和索引。可以创建虚拟列并索引。 4. **避免LIKE '%pattern'**:如引用[3]所述,这种查询性能差。 5. **分页优化**:使用引用[1]的方法优化分页。 6. **考虑搜索引擎**:如引用[4]提到的,使用Solr或Elasticsearch,它们支持通配符查询和高效后缀搜索。 在回答中,解释这些步骤。 最后,生成相关问题。 </think>以下是针对5000万数据集(5000w数据)中查询电话号码后4位(例如,查询以特定后缀如“1234”结尾的电话号码)的优化方法。由于后缀查询(如SQL的`LIKE '%1234'`)通常会导致全表扫描,性能低下(尤其在数据量巨大时),我将基于您提供的引用内容(如SQL优化技巧和分页策略)提供系统性解决方案。优化核心思路包括:**利用索引、避免全表扫描、分页处理和数据预处理**。结合引用[^3]的建议(如“不使用LIKE '%xxx'式查询”和“使用索引”),我将从实际可操作策略入手,逐步说明。 ### 优化方案概述 对于后缀查询数据库索引默认支持前缀匹配(`LIKE '1234%'`),但不支持后缀匹配(`LIKE '%1234'`)。因此,优化需通过数据转换或新增列来“模拟”前缀匹配。以下是基于您引用的关键策略(尤其引用[^1]和[^3])的推荐方法,优先级从高到低排序。 #### 1. **添新列并创建索引(最推荐方法)** - **原理**:将电话号码的后4位提取为独立列(如`last_four_digits`),并对其创建索引。这样查询时可使用精确匹配(`=`),避免低效的`LIKE`操作,索引查询时间复杂度为$O(\log n)$。 - **步骤**: - 添新列:执行ALTER TABLE添列,存储电话后缀。 - 填充数据:使用UPDATE语句一次性填充后缀数据(对于5000w数据,需分批执行以避免锁表)。 - 创建索引:在`last_four_digits`列上添B树索引。 - 查询语句:使用`SELECT * FROM table WHERE last_four_digits = '1234' LIMIT 100;`。 - **优势**:查询效率高(引用[^3]提到“避免%xxx式查询”和“使用索引”,能显著减少I/O)[^3]。 - **示例SQL**: ```sql -- 添新列 ALTER TABLE your_table ADD COLUMN last_four_digits CHAR(4); -- 分批更新数据(例如每次更新10000行,避免锁表) UPDATE your_table SET last_four_digits = RIGHT(phone_number, 4) WHERE id BETWEEN 1 AND 10000; -- 重复直至所有数据更新完成 -- 创建索引 CREATE INDEX idx_last_four_digits ON your_table(last_four_digits); -- 查询示例:查找后4位为1234的记录 SELECT id, phone_number, other_columns FROM your_table WHERE last_four_digits = '1234' LIMIT 100; -- 引用[^1]的LIMIT分页优化,防止一次性载过多数据[^1] ``` - **性能提升**:在测试环境中,类似优化可使响应时间从秒级降至毫秒级(如引用[^1]中数据查询效率提升100倍)[^1]。 #### 2. **使用反转字符串和前缀匹配(无需更改表结构)** - **原理**:将电话号码反转存储(如“12345678”变成“87654321”),然后使用`LIKE '4321%'`查询(后缀反转后变前缀)。创建反转列索引,使查询能利用索引。 - **步骤**: - 添新列:ALTER TABLE添`reversed_phone`列。 - 填充数据:UPDATE设置`reversed_phone = REVERSE(phone_number)`。 - 创建索引:在`reversed_phone`上创建索引。 - 查询语句:反转查询后缀后匹配前缀。 - **优势**:适用于不支持函数索引的老版本数据库(如MySQL 5.6),查询效率接近方法1(引用[^3]强调“避免对列的操作导致表扫描”,但此方法在数据预处理后索引有效)[^3]。 - **示例SQL**: ```sql -- 添反转列 ALTER TABLE your_table ADD COLUMN reversed_phone VARCHAR(20); -- 更新数据(分批执行) UPDATE your_table SET reversed_phone = REVERSE(phone_number) WHERE id BETWEEN 1 AND 10000; -- 创建索引 CREATE INDEX idx_reversed_phone ON your_table(reversed_phone); -- 查询:后缀1234的反转是4321,查询前缀匹配 SELECT id, phone_number, other_columns FROM your_table WHERE reversed_phone LIKE CONCAT('4321', '%') -- '4321%' 等同于前缀匹配 ORDER BY id LIMIT 100; -- 引用[^1]的分页技巧优化性能[^1] ``` #### 3. **利用数据库高级特性(如MySQL虚拟列和函数索引)** - **原理**:现代数据库MySQL 8.0+或PostgreSQL)支持生成列(generated columns)和函数索引。可直接为后缀创建虚拟索引,无需新增物理列。 - **步骤**: - 创建虚拟列:定义列自动计算后缀。 - 添索引:在虚拟列上创建索引。 - 查询:直接使用虚拟列检索。 - **优势**:零存储开销,查询时自动优化(引用[^4]提到“适配多种查询语言”,这里利用数据库内置功能)[^4]。 - **示例SQL(MySQL 8.0+)**: ```sql -- 添虚拟列 ALTER TABLE your_table ADD COLUMN last_four_digits CHAR(4) AS (RIGHT(phone_number, 4)) VIRTUAL; -- 创建索引 CREATE INDEX idx_virtual_last_four ON your_table(last_four_digits); -- 查询 SELECT * FROM your_table WHERE last_four_digits = '1234' LIMIT 100; ``` #### 4. **分页和查询语句优化(基础策略)** - **原理**:结合引用[^1]和[^3]的技巧,优化查询语句,避免全表扫描: - 使用`LIMIT`分页:防止一次性载所有数据(引用[^1]的子查询方法高效分页)。 - 避免`SELECT *`:指定所需列以减少数据传输。 - 强制索引使用:通过`FORCE INDEX`提示数据库使用索引(如有)。 - **示例SQL(后缀查询优化)**: ```sql -- 使用子查询分页(引用[^1]方法) SELECT * FROM your_table WHERE id >= (SELECT id FROM your_table WHERE last_four_digits = '1234' ORDER BY id LIMIT 9000000, 1) AND last_four_digits = '1234' LIMIT 100; -- 只查询100条 ``` - **注意事项**:如果后缀列未索引,此查询仍可能扫描大量行。因此,**强烈推荐与方法1或2结合使用**(引用[^3]警告“不做列运算”和“使用LIMIT分页”)[^3]。 #### 5. **外部工具集成(高性能替代方案)** - **原理**:对于5000w数据,如果SQL优化不足,可引入全文搜索引擎(如Elasticsearch或Solr)。它们原生支持后缀查询(wildcard queries),并行处理能力强(引用[^4]提到“支持多种查询语言如Solr或GraphQL”)[^4]。 - **步骤**: - 数据同步:使用工具(如Logstash)将数据库数据导入Elasticsearch。 - 创建索引:在Elasticsearch中定义后缀查询字段。 - 查询:使用REST API执行高效后缀搜索。 - **优势**:查询性能稳定(毫秒级),适合高频后缀搜索(如电话号码后缀过滤)。 - **示例**: ```json // Elasticsearch查询示例 GET /phone_index/_search { "query": { "wildcard": { "phone_number": "*1234" // 后缀查询 } }, "size": 100 } ``` ### 总体性能建议 - **索引是关键**:任何优化都依赖索引。后缀查询无索引时,5000w数据的全表扫描可能耗时数分钟;添索引后,响应时间可降至毫秒级(如引用[^1]的0.04s案例)[^1]。 - **数据预处理成本**:添新列或反转数据需一次性批处理(推荐夜间执行)。使用事务分批UPDATE以减少锁表(每次更新10000~100000行)。 - **监控与测试**:启用查询日志(引用[^3]建议)分析瓶颈。在测试环境验证优化效果。 - **适用场景**:这些方法适用于MySQL、PostgreSQL等关系数据库。如果数据量增长,考虑分库分表(sharding)。 通过以上策略,您可将查询效率提升数十倍至百倍(类似引用[^1]的优化效果)。如果表中已有索引或有特定约束,请提供更多细节以便针对性建议。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值