zoukankan      html  css  js  c++  java
  • SQL Server 中的三种分页方式

    USE tempdb
    GO
    SET NOCOUNT ON
    
    --创建表结构
    IF OBJECT_ID(N'ClassB', N'U') IS NOT NULL
        DROP TABLE ClassB
    GO
    CREATE TABLE ClassB(ID INT PRIMARY KEY, Name VARCHAR(16), CreateDate DATETIME, AID INT, Status INT)
    CREATE INDEX IDX_CreateDate ON ClassB(CreateDate)
    CREATE INDEX IDX_AID ON ClassB(AID)
    GO
    
    --插入测试数据
    DECLARE @ID INT
    SET @ID = 1
    WHILE @ID <= 100000
    BEGIN
        INSERT INTO ClassB VALUES(@ID, 'fx', GETDATE(), @ID % 20, @ID % 20)
        SET @ID = @ID + 1
    END
    
    --统计总行数
    SELECT 'ClassB' AS ClassB, count(1) AS Count FROM ClassB

    查询条件如下:根据CreateDate倒序排序,CreateDate一致时按照ID倒序;时间区间在2014-07-13和2014-07-14之间;每页20条,当前页数第3页;

    方案A:双Top,取出之前页的所有ID后,使用NOT IN排除这些ID进行查询,这是性能最差一种,因为他使用相同的查询谓词,查询了两次数据,且两次排序;

    DECLARE @page_size INT = 20;
    DECLARE @page_index INT = 3;
    --A: 双Top:取出之前页的所有ID后,使用NOT IN排除这些ID进行查询,性能最差
    WITH ID_List_Excluded AS
    (
        SELECT TOP (@page_size * (@page_index - 1)) ID FROM ClassB WHERE CreateDate BETWEEN '2014-07-13' AND '2014-07-14' 
        ORDER BY CreateDate DESC, ID DESC
    )
    SELECT TOP (@page_size) * FROM ClassB WHERE ID NOT IN (SELECT ID FROM ID_List_Excluded) AND CreateDate BETWEEN '2014-07-13' AND '2014-07-14' 
    ORDER BY CreateDate DESC, ID DESC;

    表 'ClassB'。扫描计数 2,逻辑读取 136 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
    表 'Worktable'。扫描计数 1,逻辑读取 197 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

    查询页数越靠后逻辑读取,下面是查询第30页的逻辑读取情况:

    表 'ClassB'。扫描计数 2,逻辑读取 1243 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。
    表 'Worktable'。扫描计数 1,逻辑读取 2574 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

    方案B:ROW_NUMBER,取出截止到当前页所有符合查询谓词的记录,并给每条记录加上序号,然后根据序号查询,性能次之;

    DECLARE @page_size INT = 20;
    DECLARE @page_index INT = 3;
    --B: ROW_NUMBER:取出截至到当前页所有符合查询谓词的记录,并给每条记录加上序号,然后根据序号查询,性能次之
    WITH NewClassB AS
    (
        SELECT TOP (@page_size * @page_index) ROW_NUMBER() OVER (ORDER BY CreateDate DESC, ID DESC) AS ROWID, * FROM ClassB 
        WHERE CreateDate BETWEEN '2014-07-13' AND '2014-07-14'
    )
    SELECT TOP (@page_size) * FROM NewClassB 
    WHERE ROWID BETWEEN (@page_size * (@page_index - 1) + 1) AND (@page_size * @page_index);

    表 'ClassB'。扫描计数 1,逻辑读取 134 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

    方案B已经比方案A快了近一倍,少了一个Worktable,同样,page_index越大,扫描次数越多,因为子查询中要查询多余的(之前页)数据和字段,下面是查询第30页时的IO读取情况;

    表 'ClassB'。扫描计数 1,逻辑读取 1241 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

    方案C:双Top,取出截止到当前页为止的所有的ID,去掉之前页的ID,然后根据ID(通常是主键)作常规查询,性能最优;

    DECLARE @page_size INT = 20;
    DECLARE @page_index INT = 3;
    WITH ID_List AS
    (
        SELECT TOP (@page_size * @page_index) ID FROM ClassB 
        WHERE CreateDate BETWEEN '2014-07-13' AND '2014-07-14' 
        ORDER BY CreateDate DESC, ID DESC
    )
    ,ID_List_Current AS
    (
        SELECT ID FROM ID_List WHERE ID NOT IN(SELECT TOP (@page_size * (@page_index - 1)) ID FROM ID_List)
    )
    SELECT * FROM ClassB WHERE ID IN (SELECT ID FROM ID_List_Current);

    表 'ClassB'。扫描计数 2,逻辑读取 52 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

    查询第30页的数据仅比查询第3页的数据多出两次逻辑读取,方案C和B的唯一在于:子查询中的查询列,方案B中返回了所有字段,所有要通过聚集索引走lookup,而方案C直接返回索引键即可。

    表 'ClassB'。扫描计数 2,逻辑读取 54 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

  • 相关阅读:
    Flutter form 的表单 input
    FloatingActionButton 实现类似 闲鱼 App 底部导航凸起按钮
    Flutter 中的常见的按钮组件 以及自 定义按钮组件
    Drawer 侧边栏、以及侧边栏内 容布局
    AppBar 自定义顶部导航按钮 图标、颜色 以及 TabBar 定义顶部 Tab 切换 通过TabController 定义TabBar
    清空路由 路由替换 返回到根路由
    应对ubuntu linux图形界面卡住的方法
    [转] 一块赚零花钱
    [转]在树莓派上搭建LAMP服务
    ssh保持连接
  • 原文地址:https://www.cnblogs.com/fengxiang/p/3841287.html
Copyright © 2011-2022 走看看