超过100w条数据表的分页查询优化

本文探讨了在高流量运营商项目中,如何应对100万+通话记录的分页查询性能瓶颈。通过引入自增主键并调整查询策略,作者分享了针对MySQL的优化方法,包括首次查询总条数、向后翻页和向前翻页的SQL调整,以及考虑将数据迁移至HBase的建议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

问题描述

解决方案

参考文献


问题描述

        最近做一个运营商的项目,其中有一个需求就是需要将用户所有的通话记录保存起来,支持按照各种条件查询。最开始开发阶段,使用的单表存储,后来根据调研,确定每天的通话量至少在100w通以上,那就只能进行分表存储,不然单表的数据量太大,后面的统计查询功能就没办法做了。按照天,每天一张表存储,但是即便这样,每天的数据量也在100w条上下,前端进行分页查询的时候,还是非常的慢,查询一次要7到8秒,这完全不能接受。

        这是因为,目前的分页查询,使用的就是最基本的OFFSET的方式,如下:

select xx from table order by xx limit xx,xx

        查询的时候,越往后分页越慢。

解决方案

        这篇博文中博主说了好几种改造的思路,都非常好,我们最后选了第一种方法来实现。这种方案其实比较直观,简单来说,就是通过改变查询基数数据大小,结合索引字段来完成的。 映射到实际开发中,我么可以通过给表添加自增主键,然后前端每次点击分页查询的时候,都把当前页最后一条记录的自增id值传递到后端,后端通过该id值去筛选基数数据大小。理解起来不复杂,但是在改造的过程有几个细节点,这里记录下来。

        (1)首次进入页面,或者首次查某一个数据的时候,默认基数还是全部数据,因为需要获取到总条数。

        (2)向后翻页的时候,前端需要把当前页的页码PageNo、预期跳转的页码ToPageNo、当前的分页大小PageSize、当前页最后一条记录的自增主键id值EndIdValue传递给后端。后端首先要确定好StartPage的大小,也就是limit的第一个值的大小,即第几页。这里要注意,不能直接使用ToPageNo,实际的StartPage = ToPageNo - PageNo。因为此时的基数数据是会排除掉PageNo页及其以前的数据的SQL参考代码如下:

select xx from table_day_xx where id>C order by id desc limit A B

        其中A =ToPageNo-PageNo,B=PageSize,C=EndIdValue

        (3)向前翻页的时候,比向后要简单些,前端需要将当前的页面PageNo,预期跳转的页面ToPageNo、当前的分页大小PageSize、当前页第一条记录的自增主键Id值StartIdValue传递给后端。这里limit的时候,可以直接使用ToPageNo,因为基数数据就是从头开始的,SQL参考如下:

select xx from table_day_xx where id<C order by id desc limit A B

        其中A=ToPageNo,B=PageSize,C=StartIdValue

        注:

        (1)分页使用的Mybatis的PageHelper的分页插件,所以默认情况是可以正常获取到基数数据的总大小及总页码的。 

        (2)前端在分页交互的时候,动作不同,对应后端查询SQL是有区别的,所以我们需要写三个SQL,分别对应首次进入、向后翻页、向前翻页三种情况的SQL。

        (3)其实这么大的数据量,存储到MySQl是不合理的,应该入到hbase,写到hadoop中比较合理的,毕竟这个查询和分析不需要那么实时,做离线的分析查看即可。

参考文献

【1】MySQL分页查询越来越慢?是时候该优化了!

【2】SQL分页过多时, 如何优化

<think>嗯,用户问的是如何将超过100万条的表数据插入到Elasticsearch里。首先,我需要考虑数据量大的时候常见的挑战和处理方法。可能用户遇到了性能问题,或者正在设计一个数据导入的方案。 首先,直接单插入肯定不行,100万条数据的话,速度太慢,网络开销也大。应该建议批量处理,比如使用Bulk API。但是批量处理的时候要注意批次大小,不能太大,否则内存可能撑不住。通常建议每批1000到5000,或者根据文档大小调整,比如每批5-15MB。用户可能需要根据实际情况测试调整。 然后,数据源是数据库,比如MySQL,可能需要用Logstash或者其他的ETL工具。Logstash的jdbc插件可以定时拉取数据,但百万级数据的话,一次性导入可能需要分页查询。不过分页查询深分页会有性能问题,可以用基于id的范围查询,或者按时间分片,这样效率更高。 另外,用程序自己处理的话,可能需要多线程或者异步的方式。比如用Java的BulkProcessor,自动管理批量提交,设置并发数,这样能提高吞吐量。不过线程数不能太多,否则ES集群压力大,可能适得其反。需要监控集群的负载情况,调整参数。 还有数据结构优化,比如避免嵌套字段太多,禁用不必要的索引,或者调整refresh_interval,暂时关闭副本,等数据导入完再恢复。这些设置能减少写入时的开销,提升速度。 还有考虑数据导入过程中出错的情况,需要有重试机制,记录失败的操作,可能用队列来暂存,然后重试。同时,监控写入速度,调整参数,比如批次大小、线程数,找到最佳组合。 可能用户还需要考虑数据更新后的同步问题,比如后续的数据变更如何同步到ES。如果是全量导入,那可能是一次性的,但如果是持续导入,可能需要binlog之类的实时同步工具,比如Canal。不过用户的问题可能只是初次导入,所以先解决当前的问题。 总结一下,步骤应该是:1. 使用Bulk API批量插入;2. 优化数据读取,比如分页或游标;3. 多线程/异步处理提高效率;4. 调整ES配置,暂时优化写入性能;5. 错误处理和重试机制;6. 完成后恢复配置,比如副本和刷新间隔。 还需要提醒用户测试不同的批次大小和线程数,因为不同的环境和数据情况可能会有不同的最优解。另外,如果数据源是数据库,是否可以使用索引来加速查询,比如按id或时间排序,减少查询时间。最后,监控ES的写入性能,观察是否有瓶颈,比如CPU、内存、磁盘IO等,及时调整。</think>将100万+数据高效插入Elasticsearch可按照以下步骤进行: 一、基础优化方案 1. 批量写入(Bulk API) ```bash POST _bulk { "index" : { "_index" : "your_index" } } { "field1":"value1", "field2":123 } { "index" : { "_index" : "your_index" } } { "field1":"value2", "field2":456 } ``` - 建议批次大小:每批1000-5000或5-15MB - Java示例使用`BulkProcessor`自动管理批次 2. 并行处理 - 推荐设置3-5个并行线程(根据集群规模调整) - Python示例: ```python from concurrent.futures import ThreadPoolExecutor def batch_insert(batch): # 执行bulk操作 with ThreadPoolExecutor(max_workers=4) as executor: executor.map(batch_insert, data_chunks) ``` 二、高级优化策略 1. 集群配置调整(临时) ```json PUT your_index/_settings { "index": { "refresh_interval": "-1", "number_of_replicas": "0" } } ``` 数据导入完成后恢复: ```json PUT your_index/_settings { "index": { "refresh_interval": "1s", "number_of_replicas": "1" } } ``` 2. 数据分片优化 ```json PUT your_index { "settings": { "number_of_shards": 5, // 根据数据总量设置 "number_of_replicas": 0 // 导入时禁用副本 } } ``` 三、数据源处理技巧 1. 数据库分页优化MySQL示例) ```sql SELECT * FROM table WHERE id > ? ORDER BY id LIMIT 5000 ``` 使用游标方式替代传统分页 2. Logstash配置示例 ```conf input { jdbc { jdbc_driver_library => "mysql-connector-java-8.0.25.jar" jdbc_driver_class => "com.mysql.jdbc.Driver" jdbc_connection_string => "jdbc:mysql://localhost:3306/db" jdbc_user => "root" jdbc_paging_enabled => true tracking_column => "id" schedule => "* * * * *" statement => "SELECT * FROM table WHERE id > :sql_last_value" } } output { elasticsearch { hosts => ["localhost:9200"] index => "your_index" document_id => "%{id}" } } ``` 四、性能监控与调优 1. 关键监控指标: - Bulk队列等待时间(cluster_metrics.thread_pools.bulk.queue) - Merge操作频率(indices_stats.merges) - CPU使用率(nodes_stats.process.cpu.percent) 2. 常见瓶颈处理: - 磁盘IO过高:升级SSD或增加节点 - 内存不足:减少批次大小,增加堆内存(建议不超过32GB) - 网络延迟:压缩传输数据(设置`http.compression: true`) 五、特殊场景处理 1. 持续增量同步方案: - 数据库CDC方案(如Debezium+ Kafka Connect) - 基于时间戳的滚动写入 2. 数据去重策略: ```json PUT your_index { "mappings": { "_meta": { "deduplication_field": "unique_id" } } } ``` 六、最佳实践建议 1. 数据预处理: - 提前转换日期格式(避免动态映射) - 禁用不必要的字段分词 - 设置合理的mapping类型 2. 写入性能基准测试(示例数据): ``` | 批次大小 | 线程数 | 写入速度(docs/s) | |----------|--------|--------------------| | 1000 | 2 | 8,000 | | 3000 | 4 | 23,000 | | 5000 | 5 | 35,000 | ``` 实际生产环境中,通过优化配置通常可以达到3-5万条/秒的写入速度。建议在正式导入前进行小批量测试(约10万条),根据测试结果调整参数。 注意:完成数据导入后务必执行`_forcemerge`操作优化段合并: ```bash POST your_index/_forcemerge?max_num_segments=1 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值