下面是一个完整的解决方案,使用 jQuery 在浏览器端解压 GZ 压缩的 SQL 文件,然后分块导入到 MySQL 数据库,并显示进度。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GZ压缩SQL文件导入工具</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<!-- 引入pako用于GZ解压 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/pako/2.0.4/pako.min.js"></script>
<style>
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
font-family: Arial, sans-serif;
}
.progress-container {
margin: 20px 0;
background-color: #f5f5f5;
border-radius: 4px;
height: 30px;
}
.progress-bar {
height: 100%;
border-radius: 4px;
background-color: #4CAF50;
width: 0%;
transition: width 0.3s;
text-align: center;
color: white;
line-height: 30px;
}
.status {
margin: 10px 0;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
min-height: 60px;
}
.btn {
background-color: #4CAF50;
color: white;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
.btn:hover {
background-color: #45a049;
}
.btn:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
#fileInfo {
margin-top: 10px;
padding: 10px;
background-color: #f9f9f9;
border-radius: 4px;
}
</style>
</head>
<body>
<div class="container">
<h1>GZ压缩SQL文件导入工具</h1>
<div>
<input type="file" id="gzFile" accept=".gz,.sql.gz">
<button id="importBtn" class="btn">解压并导入SQL文件</button>
</div>
<div id="fileInfo" style="display: none;">
<strong>文件信息:</strong>
<div>名称: <span id="fileName"></span></div>
<div>大小: <span id="fileSize"></span></div>
<div>类型: <span id="fileType"></span></div>
</div>
<div class="progress-container">
<div id="progressBar" class="progress-bar">0%</div>
</div>
<div id="status" class="status">请选择GZ压缩的SQL文件(.gz或.sql.gz)并点击导入按钮</div>
<div id="error" style="color: red;"></div>
</div>
<script>
$(document).ready(function() {
// 显示文件信息
$('#gzFile').change(function(e) {
const file = e.target.files[0];
if (!file) return;
$('#fileInfo').show();
$('#fileName').text(file.name);
$('#fileSize').text((file.size / 1024 / 1024).toFixed(2) + ' MB');
$('#fileType').text(file.type || 'application/gzip');
});
$('#importBtn').click(function() {
const fileInput = $('#gzFile')[0];
if (fileInput.files.length === 0) {
$('#error').text('请先选择GZ压缩文件');
return;
}
const file = fileInput.files[0];
// 验证文件类型
if (!file.name.match(/\.(gz|sql\.gz)$/i)) {
$('#error').text('请选择.gz或.sql.gz格式的文件');
return;
}
$('#importBtn').prop('disabled', true);
$('#status').html('开始处理文件...');
$('#error').text('');
$('#progressBar').css('width', '0%').text('0%');
const reader = new FileReader();
reader.onload = function(e) {
try {
// 使用pako解压GZ文件
$('#status').html('解压文件中...');
const compressedData = new Uint8Array(e.target.result);
const decompressedData = pako.inflate(compressedData);
const sqlContent = new TextDecoder('utf-8').decode(decompressedData);
$('#status').html('文件解压成功,准备导入数据...');
// 分割SQL内容为行
const lines = sqlContent.split('\n');
const totalLines = lines.length;
let currentLine = 0;
// 分批处理SQL文件(每批50行)
const batchSize = 50;
let batch = [];
let batchNumber = 0;
function processBatch() {
const startLine = batchNumber * batchSize;
const endLine = Math.min(startLine + batchSize, totalLines);
batch = lines.slice(startLine, endLine);
if (batch.length === 0) {
$('#status').append('<br>导入完成!');
$('#importBtn').prop('disabled', false);
return;
}
// 计算进度
currentLine = endLine;
const progress = Math.round((currentLine / totalLines) * 100);
$('#progressBar').css('width', progress + '%').text(progress + '%');
$('#status').html(`导入进度: ${progress}%<br>已处理 ${currentLine} 行,共 ${totalLines} 行`);
// 发送当前批次到服务器
$.ajax({
url: 'import_sql_chunk.php',
type: 'POST',
data: {
chunk: batch.join('\n'),
currentLine: currentLine,
totalLines: totalLines,
isLastBatch: (endLine >= totalLines)
},
success: function(response) {
try {
const data = JSON.parse(response);
if (data.success) {
batchNumber++;
// 处理下一批
setTimeout(processBatch, 100);
} else {
$('#error').text('导入错误: ' + data.message);
if (data.errors) {
$('#error').append('<br>错误示例: ' + data.errors.join(', '));
}
$('#importBtn').prop('disabled', false);
}
} catch (e) {
$('#error').text('解析响应时出错: ' + e);
$('#importBtn').prop('disabled', false);
}
},
error: function(xhr, status, error) {
$('#error').text('请求失败: ' + error);
$('#importBtn').prop('disabled', false);
}
});
}
// 开始处理第一批
processBatch();
} catch (e) {
$('#error').text('处理文件时出错: ' + e.message);
$('#importBtn').prop('disabled', false);
console.error(e);
}
};
reader.onerror = function() {
$('#error').text('读取文件时出错');
$('#importBtn').prop('disabled', false);
};
// 以ArrayBuffer形式读取文件,便于pako处理
reader.readAsArrayBuffer(file);
});
});
</script>
</body>
</html>
<?php
header('Content-Type: application/json');
// 数据库配置
$dbHost = 'localhost';
$dbUser = 'username';
$dbPass = 'password';
$dbName = 'database_name';
// 获取POST数据
$chunk = isset($_POST['chunk']) ? $_POST['chunk'] : '';
$currentLine = isset($_POST['currentLine']) ? intval($_POST['currentLine']) : 0;
$totalLines = isset($_POST['totalLines']) ? intval($_POST['totalLines']) : 0;
$isLastBatch = isset($_POST['isLastBatch']) ? $_POST['isLastBatch'] === 'true' : false;
if (empty($chunk)) {
echo json_encode(['success' => false, 'message' => '没有接收到SQL数据']);
exit;
}
// 连接数据库
$conn = new mysqli($dbHost, $dbUser, $dbPass, $dbName);
if ($conn->connect_error) {
echo json_encode(['success' => false, 'message' => '数据库连接失败: ' . $conn->connect_error]);
exit;
}
// 处理SQL块
$queries = explode(';', $chunk);
$successCount = 0;
$errorCount = 0;
$errors = [];
foreach ($queries as $query) {
$query = trim($query);
// 跳过空查询和注释
if (empty($query) || substr($query, 0, 2) == '--' || substr($query, 0, 1) == '#') {
continue;
}
// 执行SQL
if ($conn->query($query . ';') === false) {
$errorCount++;
$errors[] = $conn->error;
} else {
$successCount++;
}
}
// 关闭连接
$conn->close();
// 返回结果
if ($errorCount > 0) {
echo json_encode([
'success' => false,
'message' => "本批次处理完成 (成功: $successCount, 失败: $errorCount)",
'errors' => array_slice($errors, 0, 5) // 只返回前5个错误
]);
} else {
echo json_encode([
'success' => true,
'message' => "本批次处理完成 (成功: $successCount)",
'progress' => $currentLine / $totalLines,
'isLastBatch' => $isLastBatch
]);
}
?>
前端处理流程:
用户选择GZ压缩的SQL文件
使用FileReader API读取文件为ArrayBuffer
使用pako.js库在浏览器端解压GZ文件
将解压后的SQL内容分割成多个批次
通过AJAX将每个批次发送到服务器
实时更新进度条和状态信息
后端处理流程:
接收SQL数据块
分割成单独的SQL语句
执行每条SQL语句
返回处理结果
关键技术点:
使用pako.js进行浏览器端GZ解压,减少服务器负担
分块处理大文件,避免内存问题
详细的进度反馈
错误处理和报告
优化建议:
对于超大文件,可以增加更细粒度的进度报告
添加暂停/继续功能
实现断点续传功能
增加导入前的SQL语法快速检查
安全考虑:
文件类型验证
限制最大文件大小
SQL错误信息过滤
使用数据库预处理语句(在实际应用中应添加)
这个解决方案完全在浏览器端处理GZ解压,然后分块发送SQL到服务器执行,既高效又能提供良好的用户体验。