恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
WinForm分页控件与SQL数据库结合:从设计到避坑全指南
首页
资讯中心
/
WinForm分页控件与SQL数据库结合:从设计到避坑全指南
WinForm分页控件与SQL数据库结合:从设计到避坑全指南
发布时间:2026/10/16 3:47:25
简介面向Winform开发者的分页控件资源主要用于解决大批量数据一次性加载导致的内存占用高、界面响应慢等问题结合SQL数据库实现高效分页浏览。资源包内共65个文件以C#源码20个cs文件为核心搭配resx资源、窗体设计器文件及5个cache缓存同时包含exe可执行示例、dll库、图片和txt说明文档整体压缩包仅218KB轻量精简便于直接编译和迁移。已有837人学习适合正在开发后台管理或数据展示类Winform程序的开发者也适合希望掌握分页实现原理的初学者研读。包内提供PagerControl分页控件完整源码并配有多个示例窗体StartForm、Form1、Form2涵盖控件绘制、分页逻辑、SQL数据查询与绑定等关键环节通过示例可快速理解分页算法与数据库交互方式直接运行即可查看效果亦可按项目需求二次封装或复用节省自行开发分页功能的时间。1. 先回答一个问题WinForm 分页控件为什么必须配合 SQL 数据库一张订单表五十万行查询界面的逻辑是“把整张表查出来塞进 DataGridView”。窗口打开要卡好几秒用滚动条拖到中间界面像假死一样。这是我在一个内部订单管理工具上踩过的坑。后来换成分页控件加 SQL 数据库分页的思路查询响应从秒级降到毫秒级界面不卡了内存也稳了。这篇要讲的就是这套方案的完整落地WinForm 里怎么设计一个带 SQL 数据库的分页控件从分页方式选型、SQL 写法、事件绑定到最常见的坑每一步都给出可复现的代码和参数说明。适合正在写 WinForm 业务系统或者想把手里的全量加载改成翻页的老项目维护者。2. 分页方案怎么选内存分页和数据库分页的边界以及控件布局设计很多人一想到分页第一个念头是“把数据全查出来在内存里用 LINQ Skip/Take 切”。这个思路在小数据量下完全没问题数据一上规模就开始卡。先把两种方案的分界线讲清楚后面写控件才不会走错路。2.1 内存分页 vs 数据库分页什么时候该用哪一种内存分页的实现逻辑很直白查询一次把所有满足条件的数据放进 DataTable翻页时用table.AsEnumerable().Skip(...).Take(...)重新切出一片绑定给 DataGridView。它的优点是查询 SQL 最简单不需要懂任何分页语法一条不带分页的 SQL 配合 LINQ 就能翻页。缺点是数据一多网络传输、DataTable 存储、控件绑定的开销全部压在一次操作里。十万行数据查询耗时几秒内存多出几十兆滚动时界面每动一下都要重新绑定体感就是“假快、真卡”。数据库分页是另一个思路每次翻页都向数据库要“当前这一页”的数据SQL 里写 OFFSET 和 FETCH数据库只返回 20 行或 50 行。网络传输量小内存里只有当前页的数据。代价是每次翻页都要额外跑一次查询而且分页 SQL 必须写对 ORDER BY否则翻页会重复、漏行。两种方式的取舍用一张表说清楚对比点内存分页数据库分页查询次数1 次每次翻页 1~2 次网络传输量全表数据仅当前页数据客户端内存占用全表常驻仅当前页适用数据量千行以内万行以上翻页速度首次加载慢翻页快每次均匀受索引影响实现复杂度低中高SQL 要写对我一般给同事的判断标准很简单预估行数在几千以内用内存分页代码少、好维护过了一万特别是列表还要带筛选条件就老老实实做数据库分页。标题里既然写明“带 SQL 数据库”本篇默认走数据库分页这条线。还有一种混合方案也值得知道先内存分页等用户翻到后面发现数据量大时再切换但实际项目里这种“先跑起来再优化”的做法很容易留下两套逻辑我不太推荐直接按数据量预期选型更省心。2.2 控件长什么样DataGridView 与导航条的布局和属性设置分页控件的界面结构不复杂上下两块上面是 DataGridView占满剩余空间下面是固定高度的导航条。导航条从左到右依次是每页行数下拉、上一页按钮、页码标签、下一页按钮、跳页输入框、跳页按钮、总条数状态标签。这个排列是约定俗成的用户学习成本最低。导航条用 FlowLayoutPanel 排控件省去手动算坐标的麻烦。下面是初始化的关键代码// 每页行数下拉固定 4 档默认 20 var cmbPageSize new ComboBox { Width 60, DropDownStyle ComboBoxStyle.DropDownList }; cmbPageSize.Items.AddRange(new object[] { 10, 20, 50, 100 }); var btnPrev new Button { Text 上一页, Width 70 }; var lblPageInfo new Label { Text 1 / 1, AutoSize true }; var btnNext new Button { Text 下一页, Width 70 }; var txtJump new TextBox { Width 40, Text 1 }; var btnGo new Button { Text 跳页, Width 50 }; var lblStatus new Label { Text 共 0 条, AutoSize true }; var navPanel new FlowLayoutPanel { Dock DockStyle.Bottom, Height 32, Padding new Padding(4, 2, 4, 2) // 上下留 2px 间距视觉不挤 }; navPanel.Controls.AddRange(new Control[] { cmbPageSize, btnPrev, lblPageInfo, btnNext, txtJump, btnGo, lblStatus });几个参数要解释一下。每页行数用 DropDownList 而不是可编辑的 ComboBox是避免用户输入 0、负数、非数字少一层校验默认 20 行对大多数业务列表是合理档位翻页频率和数据量比较平衡页码用 Label 只读展示跳页才用 TextBox如果把页码也做成可编辑控件初始化时反而容易触发事件这个坑第 5 章会详细说。DataGridView 本身的属性也要设对否则用户一不小心就改了单元格dataGridView1.Dock DockStyle.Fill; dataGridView1.ReadOnly true; // 列表只读 dataGridView1.AllowUserToAddRows false; // 去掉底部空行 dataGridView1.AutoGenerateColumns true; // 列随查询结果自动生成 dataGridView1.SelectionMode DataGridViewSelectionMode.FullRowSelect; dataGridView1.RowHeadersVisible false; // 隐藏行头界面更干净AutoGenerateColumns 这个参数有争议但分页控件我建议开着分页查询返回的列就是用户要看的列自动生成省去手动配置列集合的麻烦。哪天换了查询字段列会自动跟着变维护成本低。缺点是列宽不会自适应长文本列默认显示得比较窄。实际项目里我会在绑定数据后加一个循环把内容超过列宽较多的列宽调大dataGridView1.Columns[i].Width Math.Max(dataGridView1.Columns[i].Width, 120);。如果遇到日期格式、金额对齐这种显示需求再把 AutoGenerateColumns 关掉手动建列但那是少数情况。2.3 分页控件对外暴露什么调用方只传一个查询委托分页控件最容易犯的设计错误是把 SqlConnection 直接写死在控件里。一旦换数据库、换表、换筛选条件控件就废了。常见做法是把“查询能力”抽象成一个委托控件只负责界面和翻页逻辑不知道数据从哪来。// 分页结果统一包装 public class PageResult { public int TotalCount { get; set; } public DataTable Rows { get; set; } } // 分页控件对外暴露的查询委托 public Funcint, int, PageResult PageDataLoader { get; set; } // 调用方在业务表单里注册 pagingControl.PageDataLoader (pageIndex, pageSize) { return _orderService.GetOrdersPage(pageIndex, pageSize, keyword); };委托的两个参数是 pageIndex 和 pageSize控件不管 SQL 怎么拼只负责把页码和每页行数传给调用方。这样控件层面的翻页逻辑、事件处理、状态更新全部固定下来业务查询的变化都隔离在委托实现里。委托和事件的区别也顺带说一句事件是控件主动往外喊委托是控件被动等人来填实现这里用的是后者因为分页控件本身不需要主动通知业务方“我翻页了”只需要一个“给我数据”的入口。第 6 章会把这套封装升级成 UserControl到那时这个委托就是 UserControl 对外最重要的接口。3. 分页控件的 SQL 核心OFFSET-FETCH、总条数查询与参数化写法界面只是皮SQL 才是分页控件的命。SQL 写不对界面做得再漂亮翻几页就会重复、漏行甚至直接把内存吃爆。这章把分页查询的写法、参数和优化一次讲透。3.1 两种主流 SQL 分页写法OFFSET-FETCH 与 ROW_NUMBER() 的性能差异SQL Server 从 2012 开始原生支持 OFFSET-FETCH写法最直观先排序然后跳过前面 offset 行取接下来 pageSize 行。-- SQL Server 2012 及以上 SELECT OrderID, OrderNo, CustomerName, OrderDate FROM Orders WHERE OrderDate StartDate ORDER BY OrderID OFFSET Offset ROWS FETCH NEXT PageSize ROWS ONLY;如果目标库是 SQL Server 2008 或更早没有 OFFSET-FETCH要用 ROW_NUMBER() 套一层子查询取行号落在当前页范围内的记录-- SQL Server 2008 及以下 WITH paged AS ( SELECT OrderID, OrderNo, CustomerName, OrderDate, ROW_NUMBER() OVER (ORDER BY OrderID) AS row_num FROM Orders WHERE OrderDate StartDate ) SELECT OrderID, OrderNo, CustomerName, OrderDate FROM paged WHERE row_num BETWEEN Offset 1 AND Offset PageSize ORDER BY row_num;先说共同点两种写法都必须有 ORDER BY这是分页的硬约束。数据库分页的本质是“把整个结果集排好序再切一段”没有排序切出来的页码就没有意义。再说性能差异OFFSET-FETCH 在原生产物上执行计划更干净ROW_NUMBER() 版本多了一层窗口函数计算额外开销不大但老版本数据库里确实会慢一点。真正的性能瓶颈在于 ORDER BY 列有没有索引。比如 ORDER BY OrderID 但表上主键就是 OrderID那没问题如果排序列是没索引的创建时间翻到后面几页会越来越慢因为数据库得先把前面所有行扫一遍才能跳过。我自己排查分页慢的做法是在数据库工具里打开“显示执行计划”看分页语句有没有走 Index Seek有没有出现 Sort 操作符。出现大 Sort 基本就是排序字段缺索引直接补上比调 SQL 更有效。3.2 总条数查询别偷懒COUNT、WHERE 与参数化的标准写法分页界面必须有“共多少条”和“总页数”这就要先算总数。总数和当前页数据可以用同一条 WHERE 条件拼两条 SQL放进同一次数据库往返var sql SELECT COUNT(*) FROM Orders WHERE OrderDate StartDate; SELECT OrderID, OrderNo, CustomerName, OrderDate FROM Orders WHERE OrderDate StartDate ORDER BY OrderID OFFSET Offset ROWS FETCH NEXT PageSize ROWS ONLY;; using var conn new SqlConnection(_connectionString); using var cmd new SqlCommand(sql, conn); // 日期参数用显式类型避免隐式转换导致索引失效 cmd.Parameters.Add(StartDate, SqlDbType.DateTime).Value startDate; cmd.Parameters.Add(Offset, SqlDbType.Int).Value offset; cmd.Parameters.Add(PageSize, SqlDbType.Int).Value pageSize; await conn.OpenAsync(); using var reader await cmd.ExecuteReaderAsync(); var totalCount 0; if (reader.Read()) { totalCount reader.GetInt32(0); // 第一条结果集是 COUNT } var table new DataTable(); if (reader.NextResult()) // 跳到第二条结果集 { table.Load(reader); // DataTable 加载当前页 }两个参数要重点说。第一Offset 的计算规则是 (pageIndex - 1) * pageSize页码从 1 开始第 1 页的 offset 是 0。第二日期参数不要用 AddWithValue。AddWithValue 会根据 C# 端值推断 SQL 类型经常把 DateTime 推断成 NVARCHARSQL Server 要做一次隐式转换索引就用不上了。显式写成 SqlDbType.DateTime 是分页查询保性能的习惯。把 COUNT 和分页数据放同一次批处理省一次网络往返数据量到百万级时值得做。代价是代码里要处理多结果集先读第一段拿总数再读第二段拿当前页。DataTable.Load 和 DataAdapter.Fill 的区别也提一句Load 是直接从 DataReader 灌数据配合批处理里的 NextResult 最顺手Fill 适合单独查询并依赖它自动建表结构两条 SQL 的情况用 Load 更直接。3.3 不同数据库的分页方言改一行就能切换WinForm 项目不一定都用 SQL Server业务迁移到 MySQL、Oracle 时分页逻辑本身不用变变的只有 SQL 末尾几行。页码、每页行数、偏移量的换算规则完全一致。-- MySQL / PostgreSQLLIMIT 后跟 pageSizeOFFSET 后跟 offset SELECT OrderID, OrderNo, CustomerName, OrderDate FROM Orders WHERE OrderDate StartDate ORDER BY OrderID LIMIT PageSize OFFSET Offset;-- Oracle 12c 及以上与 SQL Server 的 OFFSET-FETCH 语法一致 SELECT OrderID, OrderNo, CustomerName, OrderDate FROM Orders WHERE OrderDate StartDate ORDER BY OrderID OFFSET Offset ROWS FETCH NEXT PageSize ROWS ONLY;换数据库业务层根本不用动只改查询委托里的 SQL 方言。这也是前面刻意把数据访问放在委托里的原因数据库差异停留在“SQL 语句”这一层不进 UI。顺带提醒一句OFFSET 翻页翻得越深越慢如果业务真要看到几百页之后常规分页就吃力了可以考虑 keyset 分页即“按上一次最后一条记录的主键继续往后翻”那是另一个深度话题本篇先把常规方案做扎实。4. 把 SQL 接进控件翻页事件、当前页状态与 DataGridView 绑定SQL 写好了下一步是把它接进控件。翻页不只是“点按钮换数据”还要同步页码、总页数和按钮状态漏一项界面就会闹脾气。这一章围绕一个核心方法展开。4.1 一个 LoadPage 方法包揽所有翻页逻辑分页控件的核心可以收敛到一个方法里传入目标页码内部做边界判断、调委托拿数据、更新界面。所有翻页入口上一页、下一页、跳页、每页行数变化最后都走进这个方法避免各写各的逻辑。private int _currentPage 1; private int _pageSize 20; private int _totalPages 1; private void LoadPage(int pageIndex) { // 页码兜底不能小于 1不能超过总页数 if (pageIndex 1) pageIndex 1; if (_totalPages 0 pageIndex _totalPages) pageIndex _totalPages; var result PageDataLoader?.Invoke(pageIndex, _pageSize); if (result null) return; _currentPage pageIndex; // 总页数向上取整例如 101 条数据每页 20 行总页数是 6 _totalPages (result.TotalCount _pageSize - 1) / _pageSize; dataGridView1.DataSource result.Rows; lblPageInfo.Text ${_currentPage} / {_totalPages}; lblStatus.Text $共 {result.TotalCount} 条; // 边界时禁用按钮避免用户点出第 0 页或最后一页之外 btnPrev.Enabled _currentPage 1; btnNext.Enabled _currentPage _totalPages; }页码边界这块最容易忽略。搜索之后总页数可能从 10 变成 3这个时候用户还停在“第 5 页”LoadPage 里的兜底逻辑会把它拉回第 3 页不然界面会显示空白。总页数的公式是向上取整写成(TotalCount pageSize - 1) / pageSize不用 Math.Ceiling也避开了浮点数精度问题。每次加载都要刷新按钮的 Enabled 状态第一页时上一页置灰最后一页时下一页置灰。这是分页控件最基本的可用性要求少了它用户会在第一页继续点“上一页”然后看到一堆异常数据或者空页。4.2 事件接线上一页、下一页、跳页与每页行数变化导航条上的控件初始化后逐个接事件。常见做法是用 lambda 直接写代码集中、好读btnPrev.Click (s, e) LoadPage(_currentPage - 1); btnNext.Click (s, e) LoadPage(_currentPage 1); btnGo.Click (s, e) { if (int.TryParse(txtJump.Text, out int target) target 1 target _totalPages) { LoadPage(target); } else { // 输入非法就回写当前页码不做跳转 txtJump.Text _currentPage.ToString(); } }; cmbPageSize.SelectedIndexChanged (s, e) { if (_pageSize (int)cmbPageSize.SelectedItem) return; _pageSize (int)cmbPageSize.SelectedItem; LoadPage(1); };几个细节解释一下。跳页按钮先做 int.TryParse 和范围校验用户输入的是 “abc” 或者 “999” 时不让它进入 LoadPage只把输入框重置成当前页码。每页行数变化后回到第 1 页这是用户直觉换了一页大小列表从头看而不是停在原位。cmbPageSize 事件里先比较当前值再执行 LoadPage躲开界面初始化时的重复触发这个坑在第 5 章还会遇到。顺带可以给跳页文本框加个回车响应txtJump.KeyDown (s, e) { if (e.KeyCode Keys.Enter) btnGo.PerformClick(); };用户输完页码直接回车就能跳不用专门点按钮这是列表操作里值得加的小细节。4.3 页码越界兜底条件变化后强制回到第一页搜索框这类筛选条件变化是最容易漏的状态重置点。很多代码里数据查询方法拼了 WHERE 就重新执行忘记把当前页码改回 1。结果就是上次停在第 5 页输入关键字再查界面显示的是新条件下的“第 5 页”而新条件总共只有 2 页LoadPage 里的兜底逻辑会把它拉回第 2 页用户看到页码从 5 突然跳成 2完全看不懂发生了什么。正确做法是在搜索、清空条件、重置筛选的事件里固定重置页码private void btnSearch_Click(object sender, EventArgs e) { _currentPage 1; _totalPages 1; // 避免旧总页数参与新条件的越界判断 LoadPage(1); }先把当前页码重置为 1再调 LoadPage。这里把总页数也重置成 1 是有讲究的如果上一步查询的总页数是 20新条件只有 2 页而 LoadPage 刚进来就看到总页数还是 20就不会做越界兜底逻辑就裂了。两个状态一起重置是分页控件状态同步的基本功。日期范围筛选也是一样开始日期或结束日期一变结果集的量级完全可能不同必须走同一套重置逻辑。5. 分页控件避坑翻页重复、页码越界与事件递归的排查记录分页控件本身代码量不大写出来不复杂难的是那些藏在边界条件和事件触发里的坑。以下五条是我在不同项目里真实翻过车的记录现象、原因、解决一条条说清楚。5.1 翻页出现重复数据ORDER BY 没有唯一性现象点“下一页”上一页最后一行在新一页里又出现一次再翻一页又出现一次。来回翻页数据顺序还不完全一致。原因分页 SQL 的 ORDER BY 用的是下单日期这类非唯一字段。数据库分页的前提是“整个结果集的顺序稳定”排序列有重复值时相同值的内部排序结果可能不一样下一页开头就可能是上一页结尾。解决ORDER BY 后面附加唯一列最稳妥的就是主键。改写成ORDER BY OrderDate, OrderID。如果查询列里没有主键就先选主键分页再用主键关联出明细列。这个点不是玄学是数据库分页能不能稳定工作的前提我后来写分页 SQL 时会把“排序字段是否唯一”当成第一道自检。5.2 搜索条件一变页码越界状态没重置现象列表停在旧条件的第 5 页输入关键字点搜索界面空白或者显示的页码对不上数据。原因搜索事件里只拼了 WHERE没重置当前页码。新条件结果集小于旧页码页码越界了。解决所有会改变结果集的事件搜索、清空、日期范围变化统一走 4.3 的重置逻辑先置当前页为 1再置总页数为 1最后加载第一页。把“改查询条件必须重置页码”固化成一条写码纪律不要只在某个按钮里想起来才写。这个坑血泪教训在于表面看是“数据没出来”实际是“页码状态过期了”排查方向一旦错了会浪费不少时间。5.3 ComboBox 设置 SelectedIndex 触发递归加载现象程序启动时查询执行了两次初始化导航条时事件方法被调了好几次数据库连接开开关关。原因ComboBox 添加 Items 后会自动选中第一项触发 SelectedIndexChanged如果初始化顺序是先挂事件再赋 SelectedIndex事件就会提前跑一次 LoadPage。在事件里再去设置 cmbPageSize.SelectedIndex又会再次触发形成递归。解决两个办法配合。一是初始化顺序改成先 AddRange 填充 Items、确定 SelectedIndex最后再挂 SelectedIndexChanged 事件二是在事件开头加判断值没变就 return也就是 4.2 代码里那行if (_pageSize (int)cmbPageSize.SelectedItem) return;。第一种治本第二种防手滑我一般两个都写。5.4 分页查询还是很慢COUNT 和 SELECT * 一起拖后腿现象翻页 SQL 看着很简单但整体响应要一两秒。单独执行分页语句只要几十毫秒整体却很慢。原因慢的不是分页语句是总条数 COUNT。COUNT 要扫描整张表去数符合 WHERE 条件的行数如果 WHERE 条件上的列没有索引每次翻页都全表扫SELECT * 又把不需要的大字段也拖回来了网络和内存一起遭殃。解决给筛选条件的列建覆盖索引。比如 WHERE 里按日期范围查就在日期列上建索引COUNT 完全走索引就快了。列表查询只查要显示的列订单备注、商品描述这类大字段别进列表查询放到详情页再取。排查顺序也分享一下先看执行计划里 COUNT 是不是全表扫再看分页语句走了什么索引两个都正常最后才考虑是不是网络传输问题。5.5 列表 20 行却把整表数据拉到客户端现象每页明明只有 20 行DataGridView 绑定后内存涨得厉害翻页也变慢。原因分页 SQL 写的是 SELECT *把每行里的长文本、二进制字段全部取回客户端。数据库只返回 20 行没错但 20 行每行带 1MB 的备注文本加起来也有 20MB连续翻页就把应用内存顶上去了。解决列表查询列清单化只保留要在 DataGridView 里展示的列。如果有“查看详情”需求点击行时再按主键查一次完整记录。这个习惯和分页本身无关但和分页控件搭配时效果最明显。我在多个项目里都见过“分页了还是很卡”的问题最后根因都是 SELECT *跟分页没半点关系。6. 进阶把分页控件封装成 UserControl顺手解决异步卡顿单窗体里跑通分页只是第一步。多个窗体都要翻页列表时把分页控件封装成 UserControl 才是终点顺便把查询改成异步界面就不会假死。这章给封装骨架、异步方案和验证方法。6.1 封装成 UserControl查询能力交给调用方注入把 DataGridView、导航条、LoadPage 逻辑全部放进一个 UserControl对外只暴露一个查询委托和一个刷新入口。调用方初始化时注册委托窗体加载时调用 ReloadAsyncpublic partial class PagingGrid : UserControl { // 查询委托传入页码和每页行数返回分页结果 public Funcint, int, TaskPageResult PageDataLoader { get; set; } public async Task ReloadAsync() { await LoadPageAsync(1); } }这样换一个业务模块不需要复制一遍翻页代码只需要换委托实现。调用方写查询时把原来的同步方法改成 async 即可pagingGrid.PageDataLoader async (pageIndex, pageSize) { return await _orderService.GetOrdersPageAsync(pageIndex, pageSize, keyword); };6.2 用 async/await 让翻页不再假死同步版 LoadPage 在数据量大时会卡住 UI。改成异步版关键在于委托返回 Task数据库连接用 OpenAsync读取用 ExecuteReaderAsyncprivate async Task LoadPageAsync(int pageIndex) { if (pageIndex 1) pageIndex 1; if (_totalPages 0 pageIndex _totalPages) pageIndex _totalPages; var result await PageDataLoader?.Invoke(pageIndex, _pageSize); if (result null) return; _currentPage pageIndex; _totalPages (result.TotalCount _pageSize - 1) / _pageSize; dataGridView1.DataSource result.Rows; lblPageInfo.Text ${_currentPage} / {_totalPages}; btnPrev.Enabled _currentPage 1; btnNext.Enabled _currentPage _totalPages; }await 之后更新控件会回到 UI 线程执行不用担心跨线程访问。实际项目里我见过最典型的问题是查询丢进后台线程但忘了处理跨线程异常async/await 把这件事简化了。注意一件事连续快速点击翻页时上一次的异步查询还没回来新的一次又发出去了。简单处理是加一个请求序号只有最后一个序号的结果才允许更新界面不然会出现页面短暂跳变。这个细节在小数据量下不明显数据量大、网络波动时特别有用。6.3 怎么验证分页逻辑没写错三组数据用例分页控件改完一定要回归验证。我一般造三组测试数据第一组造 100 条数据每页 10 条翻 10 页逐页核对主键不重复、总数恒为 100第二组用不唯一的排序列复现 5.1 的翻页重复确认加上唯一列后顺序稳定第三组把一个超过 10 页的关键字收窄成只有 2 页结果确认页码自动回落。三组跑通分页控件的核心逻辑才算稳。最后说点我的习惯。早年在某个后台工具上我图省事用同步查询做过分页数据一到十万行用户每次翻页都看着窗体灰掉几秒后来被同事截图吐槽才改的异步。从那以后凡是和数据库打交道的查询我默认异步分页控件也不例外。希望帮到你。本文还有配套的精品资源点击获取