Java實(shí)現(xiàn)大數(shù)據(jù)量查詢處理的詳細(xì)方案介紹
問(wèn)題背景
在實(shí)際開(kāi)發(fā)中,經(jīng)常遇到需要查詢大量數(shù)據(jù)的場(chǎng)景,如:
- 用戶行為日志分析(百萬(wàn)級(jí)記錄)
- 訂單歷史數(shù)據(jù)導(dǎo)出(千萬(wàn)級(jí)記錄)
- 報(bào)表統(tǒng)計(jì)查詢(海量數(shù)據(jù)聚合)
- 數(shù)據(jù)遷移和同步(TB 級(jí)數(shù)據(jù))
核心挑戰(zhàn):
- 內(nèi)存溢出:數(shù)據(jù)量超過(guò) JVM 堆內(nèi)存限制
- 響應(yīng)超時(shí):查詢時(shí)間過(guò)長(zhǎng)導(dǎo)致接口超時(shí)
- 用戶體驗(yàn)差:前端長(zhǎng)時(shí)間等待或卡死
- 系統(tǒng)穩(wěn)定性:影響其他業(yè)務(wù)正常運(yùn)行
技術(shù)解決方案
1. 分頁(yè)查詢 - 基礎(chǔ)方案
@RestController
@RequestMapping("/api/data")
public class DataController {
@Autowired
private DataService dataService;
/**
* 分頁(yè)查詢大數(shù)據(jù)
*/
@GetMapping("/list")
public Result<PageResult<DataVO>> getDataList(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "100") Integer pageSize,
@RequestParam(required = false) String keyword) {
// 限制每頁(yè)最大數(shù)量,防止惡意請(qǐng)求
pageSize = Math.min(pageSize, 1000);
PageResult<DataVO> result = dataService.getDataByPage(pageNum, pageSize, keyword);
return Result.success(result);
}
}
@Service
public class DataService {
@Autowired
private DataMapper dataMapper;
public PageResult<DataVO> getDataByPage(Integer pageNum, Integer pageSize, String keyword) {
// 使用MyBatis-Plus分頁(yè)插件
Page<DataEntity> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<DataEntity> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.hasText(keyword)) {
wrapper.like(DataEntity::getName, keyword)
.or()
.like(DataEntity::getDescription, keyword);
}
Page<DataEntity> resultPage = dataMapper.selectPage(page, wrapper);
// 轉(zhuǎn)換為VO對(duì)象
List<DataVO> voList = resultPage.getRecords().stream()
.map(this::convertToVO)
.collect(Collectors.toList());
return new PageResult<>(voList, resultPage.getTotal(), pageNum, pageSize);
}
}
2. 流式查詢 - 內(nèi)存優(yōu)化方案
@Service
public class StreamDataService {
@Autowired
private SqlSessionFactory sqlSessionFactory;
/**
* 流式查詢大數(shù)據(jù),避免內(nèi)存溢出
*/
public void exportLargeData(HttpServletResponse response, String keyword) {
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment; file);
try (SqlSession sqlSession = sqlSessionFactory.openSession();
OutputStream outputStream = response.getOutputStream();
ExcelWriter excelWriter = EasyExcel.write(outputStream, DataVO.class).build()) {
DataMapper mapper = sqlSession.getMapper(DataMapper.class);
WriteSheet writeSheet = EasyExcel.writerSheet("數(shù)據(jù)").build();
// 使用MyBatis的游標(biāo)查詢,流式處理
try (Cursor<DataEntity> cursor = mapper.selectByCursor(keyword)) {
List<DataVO> batch = new ArrayList<>(1000);
for (DataEntity entity : cursor) {
batch.add(convertToVO(entity));
// 批量寫(xiě)入Excel,控制內(nèi)存使用
if (batch.size() >= 1000) {
excelWriter.write(batch, writeSheet);
batch.clear();
}
}
// 寫(xiě)入剩余數(shù)據(jù)
if (!batch.isEmpty()) {
excelWriter.write(batch, writeSheet);
}
}
} catch (Exception e) {
log.error("導(dǎo)出數(shù)據(jù)失敗", e);
throw new BusinessException("導(dǎo)出失敗");
}
}
}
// Mapper接口
@Mapper
public interface DataMapper extends BaseMapper<DataEntity> {
/**
* 游標(biāo)查詢,流式處理大數(shù)據(jù)
*/
@Select("SELECT * FROM data_table WHERE name LIKE CONCAT('%', #{keyword}, '%')")
@Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = 1000)
Cursor<DataEntity> selectByCursor(@Param("keyword") String keyword);
}
3. 異步處理 - 用戶體驗(yàn)優(yōu)化
@Service
public class AsyncDataService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private TaskExecutor taskExecutor;
/**
* 異步查詢大數(shù)據(jù)
*/
@Async("taskExecutor")
public CompletableFuture<String> queryLargeDataAsync(String taskId, QueryParam param) {
String statusKey = "task:status:" + taskId;
String resultKey = "task:result:" + taskId;
try {
// 更新任務(wù)狀態(tài)
redisTemplate.opsForValue().set(statusKey, "PROCESSING", 30, TimeUnit.MINUTES);
List<DataVO> result = new ArrayList<>();
int pageSize = 1000;
int pageNum = 1;
while (true) {
List<DataEntity> batch = dataMapper.selectByPage(param, pageNum, pageSize);
if (batch.isEmpty()) {
break;
}
// 轉(zhuǎn)換并添加到結(jié)果集
List<DataVO> voList = batch.stream()
.map(this::convertToVO)
.collect(Collectors.toList());
result.addAll(voList);
// 更新進(jìn)度
updateProgress(taskId, pageNum * pageSize);
pageNum++;
// 防止內(nèi)存溢出,分批處理
if (result.size() > 10000) {
// 可以考慮分批存儲(chǔ)到文件或緩存
storePartialResult(taskId, result);
result.clear();
}
}
// 存儲(chǔ)最終結(jié)果
redisTemplate.opsForValue().set(resultKey, result, 1, TimeUnit.HOURS);
redisTemplate.opsForValue().set(statusKey, "COMPLETED", 1, TimeUnit.HOURS);
return CompletableFuture.completedFuture(taskId);
} catch (Exception e) {
redisTemplate.opsForValue().set(statusKey, "FAILED", 1, TimeUnit.HOURS);
log.error("異步查詢失敗: taskId={}", taskId, e);
throw new RuntimeException(e);
}
}
/**
* 查詢?nèi)蝿?wù)狀態(tài)
*/
public TaskStatus getTaskStatus(String taskId) {
String statusKey = "task:status:" + taskId;
String progressKey = "task:progress:" + taskId;
String status = (String) redisTemplate.opsForValue().get(statusKey);
Integer progress = (Integer) redisTemplate.opsForValue().get(progressKey);
return new TaskStatus(taskId, status, progress);
}
}
@RestController
@RequestMapping("/api/async")
public class AsyncDataController {
@Autowired
private AsyncDataService asyncDataService;
/**
* 提交異步查詢?nèi)蝿?wù)
*/
@PostMapping("/query")
public Result<String> submitQuery(@RequestBody QueryParam param) {
String taskId = UUID.randomUUID().toString();
asyncDataService.queryLargeDataAsync(taskId, param);
return Result.success(taskId);
}
/**
* 查詢?nèi)蝿?wù)狀態(tài)
*/
@GetMapping("/status/{taskId}")
public Result<TaskStatus> getStatus(@PathVariable String taskId) {
TaskStatus status = asyncDataService.getTaskStatus(taskId);
return Result.success(status);
}
}
4. 數(shù)據(jù)庫(kù)優(yōu)化方案
@Configuration
public class DatabaseConfig {
/**
* 配置數(shù)據(jù)源,優(yōu)化大數(shù)據(jù)查詢
*/
@Bean
@Primary
public DataSource primaryDataSource() {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/db");
config.setUsername("user");
config.setPassword("password");
// 優(yōu)化大數(shù)據(jù)查詢的連接池配置
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
config.setConnectionTimeout(60000);
config.setIdleTimeout(300000);
config.setMaxLifetime(900000);
// MySQL特定優(yōu)化
config.addDataSourceProperty("useServerPrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
config.addDataSourceProperty("useCursorFetch", "true");
config.addDataSourceProperty("defaultFetchSize", "1000");
return new HikariDataSource(config);
}
}
// 優(yōu)化的查詢SQL
@Repository
public class OptimizedDataMapper {
/**
* 使用索引優(yōu)化的分頁(yè)查詢
*/
@Select("""
SELECT * FROM data_table
WHERE id > #{lastId}
AND name LIKE CONCAT('%', #{keyword}, '%')
ORDER BY id ASC
LIMIT #{pageSize}
""")
List<DataEntity> selectByIdCursor(@Param("lastId") Long lastId,
@Param("keyword") String keyword,
@Param("pageSize") Integer pageSize);
/**
* 統(tǒng)計(jì)查詢,避免COUNT(*)
*/
@Select("""
SELECT COUNT(1) FROM data_table
WHERE name LIKE CONCAT('%', #{keyword}, '%')
""")
Long countByKeyword(@Param("keyword") String keyword);
}
5. 前端優(yōu)化方案
// 虛擬滾動(dòng)組件
<template>
<div class="virtual-scroll-container" ref="container" @scroll="handleScroll">
<div class="virtual-scroll-content" :style="{ height: totalHeight + 'px' }">
<div
class="virtual-scroll-list"
:style="{ transform: `translateY(${offsetY}px)` }"
>
<div
v-for="item in visibleItems"
:key="item.id"
class="virtual-scroll-item"
:style="{ height: itemHeight + 'px' }"
>
<slot :item="item"></slot>
</div>
</div>
</div>
<!-- 加載更多 -->
<div v-if="loading" class="loading">加載中...</div>
</div>
</template>
<script>
export default {
name: 'VirtualScroll',
props: {
items: Array,
itemHeight: { type: Number, default: 50 },
bufferSize: { type: Number, default: 5 }
},
data() {
return {
containerHeight: 0,
scrollTop: 0,
loading: false
}
},
computed: {
totalHeight() {
return this.items.length * this.itemHeight
},
visibleCount() {
return Math.ceil(this.containerHeight / this.itemHeight)
},
startIndex() {
return Math.max(0, Math.floor(this.scrollTop / this.itemHeight) - this.bufferSize)
},
endIndex() {
return Math.min(this.items.length, this.startIndex + this.visibleCount + this.bufferSize * 2)
},
visibleItems() {
return this.items.slice(this.startIndex, this.endIndex)
},
offsetY() {
return this.startIndex * this.itemHeight
}
},
methods: {
handleScroll(e) {
this.scrollTop = e.target.scrollTop
// 滾動(dòng)到底部時(shí)加載更多
if (this.scrollTop + this.containerHeight >= this.totalHeight - 100) {
this.loadMore()
}
},
async loadMore() {
if (this.loading) return
this.loading = true
try {
await this.$emit('load-more')
} finally {
this.loading = false
}
}
},
mounted() {
this.containerHeight = this.$refs.container.clientHeight
}
}
</script>
6. 分片下載方案
@RestController
@RequestMapping("/api/download")
public class DownloadController {
/**
* 分片下載大文件
*/
@GetMapping("/large-data")
public ResponseEntity<StreamingResponseBody> downloadLargeData(
@RequestParam String taskId,
@RequestParam(defaultValue = "0") Long offset,
@RequestParam(defaultValue = "1048576") Long chunkSize,
HttpServletRequest request) {
StreamingResponseBody stream = outputStream -> {
try (FileInputStream fis = new FileInputStream(getDataFile(taskId))) {
fis.skip(offset);
byte[] buffer = new byte[8192];
long remaining = chunkSize;
int bytesRead;
while (remaining > 0 && (bytesRead = fis.read(buffer, 0,
(int) Math.min(buffer.length, remaining))) != -1) {
outputStream.write(buffer, 0, bytesRead);
remaining -= bytesRead;
}
}
};
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/octet-stream");
headers.add("Accept-Ranges", "bytes");
headers.add("Content-Range", String.format("bytes %d-%d/*", offset, offset + chunkSize - 1));
return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT)
.headers(headers)
.body(stream);
}
}
性能優(yōu)化策略
1. 緩存策略
@Service
public class CachedDataService {
@Cacheable(value = "dataCache", key = "#param.hashCode()",
condition = "#param.pageSize <= 100")
public PageResult<DataVO> getCachedData(QueryParam param) {
return dataService.getDataByPage(param);
}
// 使用Redis緩存大數(shù)據(jù)集的摘要信息
@Cacheable(value = "summaryCache", key = "'summary:' + #keyword")
public DataSummary getDataSummary(String keyword) {
return dataService.calculateSummary(keyword);
}
}
2. 索引優(yōu)化
-- 創(chuàng)建復(fù)合索引優(yōu)化查詢
CREATE INDEX idx_data_name_time ON data_table(name, create_time);
-- 創(chuàng)建覆蓋索引減少回表
CREATE INDEX idx_data_covering ON data_table(name, status, id, create_time);
-- 分區(qū)表優(yōu)化大數(shù)據(jù)查詢
CREATE TABLE data_table (
id BIGINT PRIMARY KEY,
name VARCHAR(255),
create_time DATETIME
) PARTITION BY RANGE (YEAR(create_time)) (
PARTITION p2023 VALUES LESS THAN (2024),
PARTITION p2024 VALUES LESS THAN (2025),
PARTITION p2025 VALUES LESS THAN (2026)
);
監(jiān)控與告警
@Component
public class DataQueryMonitor {
private final MeterRegistry meterRegistry;
@EventListener
public void handleLargeQuery(LargeQueryEvent event) {
// 記錄大數(shù)據(jù)查詢指標(biāo)
Timer.Sample sample = Timer.start(meterRegistry);
sample.stop(Timer.builder("large.query.duration")
.tag("type", event.getQueryType())
.register(meterRegistry));
// 記錄數(shù)據(jù)量
Gauge.builder("large.query.size")
.tag("type", event.getQueryType())
.register(meterRegistry, event, LargeQueryEvent::getDataSize);
// 內(nèi)存使用告警
if (event.getMemoryUsage() > 0.8) {
log.warn("大數(shù)據(jù)查詢內(nèi)存使用過(guò)高: {}%", event.getMemoryUsage() * 100);
}
}
}
最佳實(shí)踐建議
1. 查詢優(yōu)化
- 避免 SELECT:只查詢需要的字段
- 使用 LIMIT:限制返回?cái)?shù)據(jù)量
- 合理使用索引:避免全表掃描
- 分批處理:大數(shù)據(jù)分批查詢和處理
2. 內(nèi)存管理
- 流式處理:使用 Stream API 和游標(biāo)查詢
- 及時(shí)釋放:處理完數(shù)據(jù)后及時(shí)清理
- 監(jiān)控內(nèi)存:實(shí)時(shí)監(jiān)控 JVM 內(nèi)存使用情況
- 設(shè)置限制:對(duì)查詢結(jié)果數(shù)量設(shè)置上限
3. 用戶體驗(yàn)
- 異步處理:長(zhǎng)時(shí)間查詢使用異步方式
- 進(jìn)度提示:顯示查詢進(jìn)度和預(yù)估時(shí)間
- 分頁(yè)展示:前端使用虛擬滾動(dòng)或分頁(yè)
- 緩存結(jié)果:緩存常用查詢結(jié)果
總結(jié)
處理大數(shù)據(jù)量查詢需要從多個(gè)維度考慮:
- 后端優(yōu)化:分頁(yè)查詢、流式處理、異步執(zhí)行
- 數(shù)據(jù)庫(kù)優(yōu)化:索引優(yōu)化、分區(qū)表、連接池配置
- 前端優(yōu)化:虛擬滾動(dòng)、懶加載、分片下載
- 系統(tǒng)監(jiān)控:內(nèi)存監(jiān)控、性能指標(biāo)、告警機(jī)制
關(guān)鍵是要根據(jù)具體業(yè)務(wù)場(chǎng)景選擇合適的技術(shù)方案,在性能、用戶體驗(yàn)和系統(tǒng)穩(wěn)定性之間找到平衡點(diǎn)。
到此這篇關(guān)于Java實(shí)現(xiàn)大數(shù)據(jù)量查詢處理的詳細(xì)方案介紹的文章就介紹到這了,更多相關(guān)Java數(shù)據(jù)查詢內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java根據(jù)前端返回的字段名進(jìn)行查詢數(shù)據(jù)的實(shí)現(xiàn)方法
- Java如何根據(jù)前端返回的字段名進(jìn)行查詢數(shù)據(jù)
- java 根據(jù)前端返回的字段名進(jìn)行查詢數(shù)據(jù)
- Java如何使用遞歸查詢多級(jí)樹(shù)形結(jié)構(gòu)數(shù)據(jù)(多級(jí)菜單)
- Java實(shí)現(xiàn)根據(jù)sql動(dòng)態(tài)查詢并下載數(shù)據(jù)到excel
- Java使用多線程批次查詢大量數(shù)據(jù)(Callable返回?cái)?shù)據(jù))方式
- Java?Mybatis查詢數(shù)據(jù)庫(kù)舉例詳解
相關(guān)文章
SpringBoot框架內(nèi)使用Java調(diào)用訊飛星火api完整步驟
近年來(lái)人工智能技術(shù)已經(jīng)成為了各行各業(yè)中不可或缺的一部分,訊飛星火認(rèn)知是訊飛科技推出的AI開(kāi)放平臺(tái),為開(kāi)發(fā)者提供了豐富的人工智能技術(shù)接口和服務(wù),這篇文章主要介紹了SpringBoot框架內(nèi)使用Java調(diào)用訊飛星火api的相關(guān)資料,需要的朋友可以參考下2025-05-05
解決SpringBoot啟動(dòng)警告:OpenJDK?64-Bit?Server?VM?warning
這篇文章主要介紹了解決SpringBoot啟動(dòng)警告:OpenJDK?64-Bit?Server?VM?warning的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),對(duì)同樣遇到這個(gè)問(wèn)題的朋友具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2025-11-11
SpringBoot與Spring中數(shù)據(jù)緩存Cache超詳細(xì)講解
我們知道內(nèi)存讀取速度遠(yuǎn)大于硬盤(pán)讀取速度,當(dāng)需要重復(fù)獲取相同數(shù)據(jù)時(shí),一次一次的請(qǐng)求數(shù)據(jù)庫(kù)或者遠(yuǎn)程服務(wù),導(dǎo)致在數(shù)據(jù)庫(kù)查詢或者遠(yuǎn)程方法調(diào)用上小號(hào)大量的時(shí)間,最終導(dǎo)致程序性能降低,這就是數(shù)據(jù)緩存要解決的問(wèn)題,學(xué)過(guò)計(jì)算機(jī)組成原理或者操作系統(tǒng)的同學(xué)們應(yīng)該比較熟悉2022-10-10
Java 轉(zhuǎn)義字符全解析從基礎(chǔ)語(yǔ)法到安全編碼實(shí)踐(最新推薦)
本文詳細(xì)介紹了Java中的轉(zhuǎn)義字符,包括基礎(chǔ)轉(zhuǎn)義序列、Unicode轉(zhuǎn)義、Java 15的文本塊特性以及在正則表達(dá)式和安全編碼中的應(yīng)用,通過(guò)實(shí)例和最佳實(shí)踐,幫助開(kāi)發(fā)者正確使用轉(zhuǎn)義字符,提高代碼的正確性和安全性,感興趣的朋友跟隨小編一起看看吧2025-10-10
Java如何實(shí)現(xiàn)圖片裁剪預(yù)覽功能
通常注冊(cè)賬戶上傳用戶圖像時(shí)需要進(jìn)行預(yù)覽,這篇文章就是教我們?nèi)绾斡?Java 實(shí)現(xiàn)圖片裁剪預(yù)覽功能,需要的朋友可以參考下2015-07-07
Java中shiro框架和security框架的區(qū)別
這篇文章主要介紹了Java中shiro框架和security框架的區(qū)別,shiro和security作為兩款流行的功能強(qiáng)大的且易于使用的java安全認(rèn)證框架,在近些年中的項(xiàng)目開(kāi)發(fā)過(guò)程中使用廣泛,今天我們就來(lái)一起了解一下兩者的區(qū)別2023-08-08
Springmvc調(diào)用存儲(chǔ)過(guò)程,并返回存儲(chǔ)過(guò)程返還的數(shù)據(jù)方式
這篇文章主要介紹了Springmvc調(diào)用存儲(chǔ)過(guò)程,并返回存儲(chǔ)過(guò)程返還的數(shù)據(jù)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11

