36848条MySQL数据PHP+jQuery加载渲染卡顿(耗时2分30秒)优化方案咨询
如何优化3万+数据的Ajax获取与前端渲染性能?
我需要从MySQL数据库中获取36848条数据,之后通过jQuery在网页上格式化并追加渲染。这两个操作总共耗时2分30秒,在此期间页面完全无法进行任何其他操作,根本没法用。我已经尝试过将SELECT查询限制为仅获取所需字段,但处理时间并没有改善,怀疑耗时的环节是jQuery的页面渲染而非SQL查询,请问该怎么优化?
我的PHP函数如下:
<?php function get_my_customers() { $req = $bdd->prepare("SELECT * FROM clients ORDER BY raison_sociale"); $req->execute(); $customers = $req->fetchAll(PDO::FETCH_ASSOC); return $customers; } if(isset($_POST['ajax'])){ $result = get_my_customers(); echo json_encode($result); } ?>
jQuery(Ajax)数据获取与渲染代码:
function get_my_customers(){ var customers; $.ajax({ url: "model/application/*******/customers/get_my_customers.php", async: false, type: "POST", dataType: 'json', data: {ajax: 'true'}, success: function(data) { customers = data; } }); return customers; } var my_customers = get_my_customers(); $('#customers_list').append('<div class="list_card" card="customers_list"></div>'); $.each(my_customers, function(key, val){ if(val.enseigne == null){ var enseigne = ''; }else{ var enseigne = ',' + val.enseigne; } $('.list_card[card="customers_list"]').append(''+ '<div class="list_card_element">'+ '<div><b>'+ val.numero_client +'</b></div>'+ '<div>'+ '<b>'+ val.raison_sociale +'</b>' + enseigne + '<br>'+ val.cp_livraison + ', ' + val.adresse_livraison + '</div>'+ '</div>' ); }
核心问题分析
首先,你的代码里有两个致命的性能杀手:
- 同步Ajax请求(
async: false):这会直接阻塞浏览器主线程,导致页面完全无法响应任何操作,直到请求完成——这就是你觉得页面"假死"的直接原因。 - 高频DOM操作:循环里每次调用
append()都会触发浏览器的重排(reflow)和重绘(repaint),3万多次的DOM操作会把性能拖到谷底,这确实是你耗时的主要原因。
另外,一次性返回3万条数据的JSON,不仅传输时间长,还会占用大量内存,也是性能瓶颈之一。
分步优化方案
1. 立即移除同步Ajax,改用异步模式
把async: false删掉,同步请求在现代浏览器里已经被逐步废弃,而且完全没必要。调整后的Ajax代码可以改成这样:
function get_my_customers(callback){ $.ajax({ url: "model/application/*******/customers/get_my_customers.php", type: "POST", dataType: 'json', data: {ajax: 'true'}, success: function(data) { callback(data); // 用回调处理数据,而不是阻塞等待返回 } }); } // 调用方式改成异步回调 get_my_customers(function(my_customers){ // 在这里处理渲染逻辑 renderCustomers(my_customers); });
2. 优化前端渲染:减少DOM操作次数
不要每次循环都去操作DOM,而是先把所有HTML内容拼接成一个字符串,或者用DocumentFragment一次性插入DOM,这样只会触发一次重排重绘。
方案一:字符串拼接(简单高效)
function renderCustomers(customers){ var $listCard = $('<div class="list_card" card="customers_list"></div>'); var html = ''; // 先把所有元素的HTML拼到字符串里 $.each(customers, function(key, val){ var enseigne = val.enseigne ? ',' + val.enseigne : ''; html += ` <div class="list_card_element"> <div><b>${val.numero_client}</b></div> <div> <b>${val.raison_sociale}</b>${enseigne}<br> ${val.cp_livraison}, ${val.adresse_livraison} </div> </div> `; }); $listCard.html(html); // 一次性把所有内容插入到容器 $('#customers_list').append($listCard); }
方案二:使用DocumentFragment(更规范的DOM操作方式)
function renderCustomers(customers){ var $listCard = $('<div class="list_card" card="customers_list"></div>'); var fragment = document.createDocumentFragment(); // 创建文档碎片,不会触发重排 $.each(customers, function(key, val){ var enseigne = val.enseigne ? ',' + val.enseigne : ''; var $element = $(` <div class="list_card_element"> <div><b>${val.numero_client}</b></div> <div> <b>${val.raison_sociale}</b>${enseigne}<br> ${val.cp_livraison}, ${val.adresse_livraison} </div> </div> `); fragment.appendChild($element[0]); // 先把元素加到碎片里 }); $listCard.append(fragment); // 碎片一次性插入容器 $('#customers_list').append($listCard); }
3. 后端优化:分批加载数据(分页/滚动加载)
一次性返回3万条数据太夸张了,用户根本不可能一次性看完所有内容。最好改成分页加载或者滚动加载(无限滚动),每次只返回几十条数据,这样传输和渲染都会快很多。
比如后端改成支持分页:
<?php function get_my_customers($page = 1, $limit = 50) { $offset = ($page - 1) * $limit; // 只查询需要的字段,你已经试过,但要确保确实只选需要的,不要用SELECT * $req = $bdd->prepare("SELECT numero_client, raison_sociale, enseigne, cp_livraison, adresse_livraison FROM clients ORDER BY raison_sociale LIMIT :offset, :limit"); $req->bindParam(':offset', $offset, PDO::PARAM_INT); $req->bindParam(':limit', $limit, PDO::PARAM_INT); $req->execute(); $customers = $req->fetchAll(PDO::FETCH_ASSOC); return $customers; } if(isset($_POST['ajax'])){ $page = isset($_POST['page']) ? (int)$_POST['page'] : 1; $limit = isset($_POST['limit']) ? (int)$_POST['limit'] : 50; $result = get_my_customers($page, $limit); // 可以返回总条数,方便前端判断是否还有更多数据 echo json_encode([ 'data' => $result, 'total' => $bdd->query("SELECT COUNT(*) FROM clients")->fetchColumn() ]); } ?>
然后前端改成滚动加载,比如监听页面滚动,当滚动到底部时加载下一页:
var currentPage = 1; var isLoading = false; var total = 0; function loadCustomers(page){ if(isLoading) return; isLoading = true; $.ajax({ url: "model/application/*******/customers/get_my_customers.php", type: "POST", dataType: 'json', data: {ajax: 'true', page: page, limit: 50}, success: function(res) { total = res.total; renderCustomers(res.data); // 调用之前的渲染函数,追加内容 currentPage++; isLoading = false; } }); } // 初始化加载第一页 loadCustomers(1); // 监听滚动事件,加载下一页 $(window).scroll(function(){ if($(window).scrollTop() + $(window).height() >= $(document).height() - 100){ if(currentPage * 50 < total){ loadCustomers(currentPage); } } }); // 修改渲染函数,改成追加模式(而不是每次创建新的list_card) function renderCustomers(customers){ var $listCard = $('.list_card[card="customers_list"]'); if(!$listCard.length){ $listCard = $('<div class="list_card" card="customers_list"></div>'); $('#customers_list').append($listCard); } var html = ''; $.each(customers, function(key, val){ var enseigne = val.enseigne ? ',' + val.enseigne : ''; html += ` <div class="list_card_element"> <div><b>${val.numero_client}</b></div> <div> <b>${val.raison_sociale}</b>${enseigne}<br> ${val.cp_livraison}, ${val.adresse_livraison} </div> </div> `; }); $listCard.append(html); }
4. 其他小优化
- 缓存DOM选择器:比如把
$('.list_card[card="customers_list"]')提前缓存起来,不要每次循环都去查找DOM。 - 避免在循环里做复杂计算:比如
enseigne的判断可以简化成三元表达式,减少代码复杂度。 - 给数据库表加索引:
ORDER BY raison_sociale可以给raison_sociale字段加索引,加快SQL查询速度(虽然你之前说查询不是瓶颈,但加索引没坏处)。
按照这些优化方式,你的页面性能会有质的提升,不仅不会再假死,渲染速度也会快很多。
内容的提问来源于stack exchange,提问作者Beko
相关产品推荐
相关产品推荐

