基于Python(Flask/PostgreSQL)实现DataTables服务器端处理问询
解决Flask+PostgreSQL大数据表格加载慢的服务器端分页方案
我来帮你搞定这个34000条数据加载慢的问题,服务器端分页是最直接有效的解决方案——每次只请求当前页的数据,避免一次性传输和渲染几万条记录。结合你的技术栈,我给你分步骤实现:
核心问题分析
你当前的代码是一次性把test表所有数据查询出来并渲染,这会导致三个大问题:
- PostgreSQL查询大量数据耗时久
- 服务器到前端的网络传输数据量过大
- 前端浏览器渲染几万条DOM节点卡顿
服务器端分页的核心思路是:每次只查询当前页需要的N条数据,同时获取总数据量用于计算分页页码。
方案一:传统页面刷新式分页(适配你现有Bootstrap分页JS)
这个方案不需要大改前端逻辑,通过URL参数传递页码,后端返回对应页的数据。
1. 修改Flask后端路由(main.py)
更新home函数,处理分页参数,用PostgreSQL的LIMIT和OFFSET实现分页查询:
from flask import request @login_required def home(): # 获取分页参数,默认第1页,每页8条(和你现有JS的分页数量匹配) page = request.args.get('page', 1, type=int) per_page = request.args.get('per_page', 8, type=int) connect() # 连接PG数据库 # 1. 查询当前页的数据 connect.cur.execute(""" SELECT * FROM test LIMIT %s OFFSET %s """, (per_page, (page - 1) * per_page)) test_execute = connect.cur.fetchall() # 2. 查询总数据条数,用于计算总页数 connect.cur.execute("SELECT COUNT(*) FROM test") total_count = connect.cur.fetchone()[0] total_pages = (total_count + per_page - 1) // per_page # 向上取整计算总页数 # 保留你原有的统计逻辑 count_equipement() return render_template( 'index.html', value=test_execute, value2=count_equipement.nb_equipement, value3=check_ok.nb_ok, value4=check_ko.nb_ko, current_page=page, total_pages=total_pages, per_page=per_page, total_count=total_count )
2. 更新前端模板(index.html)
在表格下方添加分页组件,同时保留原有表格渲染逻辑:
<!-- 原有表格代码保持不变 --> <table class="table table-bordered" id="dataTable" width="100%" cellspacing="0"> <!-- ... 你的thead、tfoot、tbody代码 ... --> </table> <!-- 新增分页组件 --> <nav aria-label="Page navigation" class="mt-4"> <ul class="pagination justify-content-center"> <!-- 上一页按钮 --> <li class="page-item {% if current_page == 1 %}disabled{% endif %}"> <a class="page-link" href="{{ url_for('home', page=current_page-1, per_page=per_page) }}" aria-label="Previous"> <span aria-hidden="true">«</span> </a> </li> <!-- 页码按钮(优化显示:只展示当前页前后2个页码,避免过多) --> {% set start_page = max(1, current_page - 2) %} {% set end_page = min(total_pages, current_page + 2) %} {% for p in range(start_page, end_page+1) %} {% if p == current_page %} <li class="page-item active"><a class="page-link" href="{{ url_for('home', page=p, per_page=per_page) }}">{{ p }}</a></li> {% else %} <li class="page-item"><a class="page-link" href="{{ url_for('home', page=p, per_page=per_page) }}">{{ p }}</a></li> {% endif %} {% endfor %} <!-- 下一页按钮 --> <li class="page-item {% if current_page == total_pages %}disabled{% endif %}"> <a class="page-link" href="{{ url_for('home', page=current_page+1, per_page=per_page) }}" aria-label="Next"> <span aria-hidden="true">»</span> </a> </li> </ul> </nav> <!-- 新增数据统计提示 --> <div class="text-center text-muted"> 显示第 {{ (current_page-1)*per_page + 1 }} 到 {{ min(current_page*per_page, total_count) }} 条,共 {{ total_count }} 条数据 </div>
方案二:AJAX异步分页(无刷新更流畅)
如果想实现无刷新分页,体验更好,可以用AJAX动态加载数据,不用刷新整个页面。
1. 新增Flask API路由(main.py)
专门用于返回分页数据的JSON接口:
from flask import jsonify @login_required def test_data_api(): page = request.args.get('page', 1, type=int) per_page = request.args.get('per_page', 8, type=int) connect() # 查询当前页数据 connect.cur.execute(""" SELECT * FROM test LIMIT %s OFFSET %s """, (per_page, (page - 1) * per_page)) data = connect.cur.fetchall() # 查询总数据量 connect.cur.execute("SELECT COUNT(*) FROM test") total_count = connect.cur.fetchone()[0] total_pages = (total_count + per_page - 1) // per_page # 格式化数据为JSON友好的结构 formatted_data = [] for row in data: formatted_data.append({ 'col1': row[0], 'col2': row[1], 'col3': row[2], 'col4': row[3], 'col5': row[4] }) return jsonify({ 'data': formatted_data, 'current_page': page, 'total_pages': total_pages, 'total_count': total_count })
记得在Flask app中注册这个路由:
app.add_url_rule('/test-data', 'test_data_api', test_data_api)
2. 前端AJAX实现(index.html)
修改表格和分页部分,用JS动态渲染:
<table class="table table-bordered" id="dataTable" width="100%" cellspacing="0"> <thead> <tr> <th>First</th> <th>Second</th> <th>Third</th> <th>Fourth</th> <th>Fifth</th> </tr> </thead> <tfoot> <tr> <th>First</th> <th>Second</th> <th>Third</th> <th>Fourth</th> <th>Fifth</th> </tr> </tfoot> <tbody id="tableBody"> <!-- 内容由JS动态加载 --> </tbody> </table> <!-- 分页容器 --> <nav aria-label="Page navigation" class="mt-4"> <ul class="pagination justify-content-center" id="pagination"> <!-- 内容由JS动态生成 --> </ul> </nav> <script> // 初始化加载第一页 loadPage(1); function loadPage(page) { const per_page = 8; fetch(`/test-data?page=${page}&per_page=${per_page}`) .then(response => response.json()) .then(res => { // 渲染表格内容 const tableBody = document.getElementById('tableBody'); tableBody.innerHTML = ''; res.data.forEach(row => { const tr = document.createElement('tr'); tr.innerHTML = ` <td>${row.col1}</td> <td><a href="{{ url_for('site', site_id='') }}${row.col2}">${row.col2}</a></td> <td>${row.col3}</td> <td>${row.col4}</td> <td>${row.col5}</td> `; tableBody.appendChild(tr); }); // 渲染分页按钮 const pagination = document.getElementById('pagination'); pagination.innerHTML = ''; // 上一页按钮 const prevLi = document.createElement('li'); prevLi.className = `page-item ${res.current_page === 1 ? 'disabled' : ''}`; prevLi.innerHTML = `<a class="page-link" href="#" onclick="loadPage(${res.current_page - 1})" aria-label="Previous"><span aria-hidden="true">«</span></a>`; pagination.appendChild(prevLi); // 页码按钮 const startPage = Math.max(1, res.current_page - 2); const endPage = Math.min(res.total_pages, res.current_page + 2); for (let p = startPage; p <= endPage; p++) { const li = document.createElement('li'); li.className = `page-item ${p === res.current_page ? 'active' : ''}`; li.innerHTML = `<a class="page-link" href="#" onclick="loadPage(${p})">${p}</a>`; pagination.appendChild(li); } // 下一页按钮 const nextLi = document.createElement('li'); nextLi.className = `page-item ${res.current_page === res.total_pages ? 'disabled' : ''}`; nextLi.innerHTML = `<a class="page-link" href="#" onclick="loadPage(${res.current_page + 1})" aria-label="Next"><span aria-hidden="true">»</span></a>`; pagination.appendChild(nextLi); }) .catch(err => console.error('加载数据失败:', err)); } </script>
额外优化建议
- 避免重复连接数据库:不要每次请求都调用
connect(),改用连接池(比如psycopg2.pool.SimpleConnectionPool),减少数据库连接开销。 - **不要用SELECT ***:只查询你需要的列,比如
SELECT col1, col2, col3 FROM test,减少数据传输量。 - 添加数据库索引:如果后续需要按某些列筛选/排序,提前给这些列加索引,提升查询速度。
- 适配DataTables(如果你的Bootstrap主题用了这个):如果你的
dataTable是DataTables插件,直接开启它的serverSide模式,配置更简单,官方文档有完整的服务器端处理示例。
内容的提问来源于stack exchange,提问作者AChichi
相关产品推荐
相关产品推荐

