老生常谈:也来讨论一下SQL分页的问题

SQL分页技巧

虽然说这个话题真的是外婆级的话题,但却是在我们的开发中经常遇到的问题。一般,我们会抛弃在RecordSet中分页的做法,因为显而易见的原因,这种方法的效率是非常低的。于是SQL分页是一个唯一的选择。

我一直是使用下面这种分页语句:

SELECT   *   FROM  ( SELECT   TOP  页大小  *    FROM  ( SELECT   TOP  起始记录 + 页大小  *    FROM  表  ORDER   BY  排序字段  DESC ) t1   ORDER   BY  排序字段) t2  ORDER   BY  排序字段  DESC

这种分页方法通用、效率也不低,但有一个致命的问题,就在于最后一页的处理上。如果最后一页不足额定的每页显示条数(页大小),则会从前一页拉一些记录来凑数。

最近我又从网上找了几种流行的SQL分页,综合考虑效率与通用性,我测试了以下两种: 
   
方案一:利用Not In和SELECT TOP分页

SELECT   TOP  页大小  *   FROM  表  WHERE  (主键  NOT   IN  ( SELECT   TOP  起始记录  主键  FROM  表  ORDER   BY  排序字段))  ORDER   BY  排序字段

方案二:利用ID大于多少和SELECT TOP分页

SELECT   TOP  页大小  *   FROM  表  WHERE  (排序字段  >  ( SELECT   MAX (排序字段)  FROM  ( SELECT   TOP  起始记录  排序字段  FROM  表  ORDER   BY  排序字段)  AS  T))  ORDER   BY  排序字段

加上我常用的那个:利用SELECT TOP来回倒分页,算是方案三吧。一起做了一个简单测试。

先创建表t_hello,很简单的一个表:

CREATE   TABLE   [ t_hello ]  (
    
[ sysid ]   [ uniqueidentifier ]   NOT   NULL   CONSTRAINT   [ DF_t_hello_sysid ]   DEFAULT  ( newid ()),
    
[ cdate ]   [ datetime ]   NOT   NULL   CONSTRAINT   [ DF_t_hello_cdate ]   DEFAULT  ( getdate ()),
    
[ title ]   [ nvarchar ]  ( 50 ) COLLATE Chinese_PRC_CI_AS  NULL  ,
    
CONSTRAINT   [ PK_t_hello ]   PRIMARY   KEY    CLUSTERED  
    (
        
[ sysid ]
    )  
ON   [ PRIMARY ]  
ON   [ PRIMARY ]
GO

 然后插入100万条记录,呵呵,有点变态。就可以测试了,写段代码:

SqlConnection connection  =   null ;
            
try
{
connection 
= new SqlConnection(conStr);
connection.Open();

SqlCommand cmd 
= new SqlCommand();
cmd.Connection
=connection;
cmd.CommandTimeout
=connection.ConnectionTimeout;            
                
foreach(int start in starts)
{
cmd.CommandText
="相应的SQL语句";
SqlDataReader reader 
= cmd.ExecuteReader();
reader.Close();
}

connection.Close();
}

catch (Exception ex)
{
if( connection != null ) connection.Close();
}

其中的starts 是一个int数组,里面有 {10,500,2000,10000,500000}。分别套用三个不同的SQL语句,结果如下: 

方案 1
开始:13:08:02.408145
结束:13:08:19.793144
Test 1 持续时间:00:00:17.3849984

方案 2
开始:13:08:19.803158
结束:13:08:20.033489
Test 2 持续时间:00:00:00.2303312

方案 3
开始:13:08:20.043504
结束:13:08:24.449840
Test 3 持续时间:00:00:04.4063360


大家也可以参考squirrel_sc做的测试,http://www.cnblogs.com/squirrel_sc/archive/2004/10/02/48583.html以及http://blog.youkuaiyun.com/lihonggen0/archive/2004/09/14/103511.aspx

第二种方案的效率真是太明显了(当然如果cdate没有建立索引,它的效率也非常低)。第一方案Not In的方法显然不太可能成为我们的选择。我是看上了方案二的如此高的效率,但进一步研究却发现这个SQL语句在使用中还有不少要注意的地方。

首先,这个MAX(id)对id这个字段是有要求的,GUID就不能用了,这不算太什么,反正我也不会用guid来排序。一般排序的字段也就是日期、名称之类的。

其次,我常用的按日期降序排列(降序应该是最常用的排法了吧?),那这句话就变成了:

SELECT   TOP   ' +cast(@size as nvarchar(8))+ '   *  
FROM  t_hello 
WHERE  (
    cdate 
<  (  SELECT   MIN (cdate)  FROM  ( SELECT   TOP   ' +cast(@start as nvarchar(8))+ '  cdate  FROM  t_hello  ORDER   BY  cdate  desc AS  T )
    ) 
ORDER   BY  cdate  desc

除了要加上DESC以外,还有两处变化(一是把大小变成小于,二是把MAX变成MIN)。

第三,上面的句子还有点问题,从网上看到 的原始语句中"起始记录(start)"是"页数*页大小"。这里的页数有问题,能不能取0呢?如果取0,TOP语句就会出错,如果不取0,1*页大小, 那第一页的内容就会丢掉。(大家可以试一下,比如1*20)用起始记录也一样,从1开始还是从0开始,所以需要把小于号变成“小于等于”,同理,大于号也 要变成“大于等于”。然后,不用页数*页大小,改成“起始记录”,就解决问题了。

总得来说,方案二的确是一个不错的方法。:)

 
### PHP常见安全漏洞及其成因 #### SQL 注入 SQL注入发生在应用程序构建动态SQL语句时未能正确处理用户输入的情况。当开发者直接将未经验证或转义的用户数据嵌入到SQL查询字符串中,攻击者可以通过构造特定的输入来改变原始查询逻辑,从而执行任意SQL命令。 防止措施包括使用预编译语句和参数化查询代替字符串拼接;对于确实需要动态生成的部分,则应严格采用白名单方式对特殊字符进行转义[^1]。 #### 全局变量注册漏洞 此问题源于早版本PHP配置不当,默认启用了`register_globals`选项,使得GET/POST/Cookie中的参数可以直接作为全局变量访问。这允许恶意用户操纵程序内部状态,甚至覆盖重要设置。现代环境中通常已禁用该特性,但仍需注意框架级的安全实践以避免类似风险。 解决方案在于关闭`register_globals`指令,并始终显式初始化所有使用的变量,在接收外部输入前实施严格的类型检查与消毒流程[^2]。 #### fwrite 函数未过滤 如果向fwrite传递的数据源来自不可信渠道而缺乏必要的净化机制,就可能引入潜在威胁。比如写入含有HTML标签或者脚本片段的内容至日志或其他持久存储介质里,一旦这些记录被重新读取展示给其他用户浏览的话,便构成了跨站点脚本(XSS)隐患。 应当确保任何要输出的信息都经过适当编码转换,去除掉非法成分后再做进一步处理[^3]。 #### 权限校验疏忽 无论是前端还是后端接口设计阶段都要重视权限控制环节的设计合理性。缺少有效的身份认证以及细粒度授权策略容易造成越权行为的发生——未经授权即能获取敏感资料或是执行特权动作。特别是管理后台部分更应该加强防护力度,限定只有具备相应角色资格的人才可以触及核心业务功能模块。 建立完善的RBAC模型(基于角色的访问控制系统),并对每一次请求均加以审核确认其合法性是应对这类挑战的有效手段之一[^4]。 #### 文件上传缺陷 不加甄别的接受客户端提交过来的各种文件存在极大危险性,因为它们可能会携带病毒木马亦或者是精心准备过的特制文档用来触发服务器上的某些弱点进而取得更高层次的操作权限。因此有必要制定一套完整的检验规则涵盖但不限于MIME类型判断、扩展名校验、尺寸上限设定等方面的工作。 此外还需考虑隔离存放位置以防万一发生意外情况影响正常服务运行状况。例如指定专门目录用于暂存待审材料并且赋予最低限度可满足需求的读写许可级别即可。 #### Unserialize 反序列化漏洞 Phar文件格式支持自定义元数据字段且是以序列化的形态保存下来这一点扩大了PHP反序列化漏洞的影响范围。即使没有调用unserialize()函数本身也能借助于诸如file_exists(), is_dir()之类的底层I/O API加上phar://伪协议实现间接性的对象实例恢复过程。期间若有涉及到了重载方法如__destruct,__toString等则极有可能成为发动攻击的关键入口点。 为了防范此类事件的发生建议尽可能减少不必要的类成员属性暴露程度同时密切关注官方发布的补丁更新及时修补已知脆弱之处。 ```php // 防范反序列化漏洞的一个例子 class SafeClass { private $data; public function __construct($safeData){ $this->data = htmlspecialchars($safeData); } // 禁止自动加载未知类 static function __set_state(array $properties){ throw new Exception("Deserialization not allowed"); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值