数据库.编译
编译 .啥是编译
当SQLSERVER收到任何一个指令,包括查询(query)、批处理(batch)、存储过程、触发器(trigger)、预编译指令(prepared statement)和动态SQL语句(dynamic SQL Statement)要完成语法解释、语句解释,
然后再进行“编译(compile)”,生成能够运行的“执行计划(execution plan)”。
这也是一个递归的解释,用编译来解释编译....
Console.WriteLine("Hello_SQL");
编译=>["Hello_SQL.exe/Hello_SQL.dll","中间语言(MSIL)"]
用更了解的C#语言来讲,当Vs收到一段代码,执行"生成解决方案",(调用"CSC.exe"程序),
完成["词法分析","语法分析","语义分析","中间代码生成","代码优化"...],
生成能够运行的exe/dll,MSIL语言.
其实我们就是把编译器看作一个黑盒子即可,
可以将源程序(SQL,C#,C等)映射为在语义上等价的目标程序(执行计划,exe,.s等)
编译其实就是将高级语言等价的转换为离机器语言更近一步的一个操作,
执行源程序的很重要的一步.
如果有兴趣的话,可以看一下<编译原理>这本书.
毕竟本人水平有限,
不要指望我能把编译讲出一朵花来,我只是介绍了一下概念,
我目的只是为了大家了解,执行SQL语句,有这么一步.
只要记住执行SQL语句会要先编译就行了.
编译Vs执行计划
在编译的过程中,SQLSERVER会根据所涉及的对象的架构(schema)、统计信息以及指令的具体内容,估算可能的执行计划,
以及他们的成本(cost),最后选择一个SQLSERVER认为成本最低的执行计划来执行。执行计划生成之后,
SQLSERVER通常会把他们缓存在内存里,术语统称他们叫“plan cache”以后同样的语句执行,SQLSERVER就可以使用同样的执行计划,而无须再做一次编译。
这种行为叫“重用(reuse)或者叫重用执行计划”。
查询流程
流程描述:
首先,在计划缓存中查找是否包含新的查询,如果包含则直接交由执行引擎来执行该缓存计划,跳过编译阶段。
其次,如果没有匹配则执行分析阶段(包括参数化、并将SQL文本转化成逻辑树作为下一个阶段的输入),
再次检查缓存后是否包含,包含则交给执行引擎,否则继续下一步。
第三,代数化。
第四,优化并将新计划交给执行引擎。
编译Vs重编译
但是有时候,哪怕是一模一样的语句,SQL下次执行还是要再做一次编译。
这种行为叫“重编译(recompile)”。
编译和重编译都是要消耗资源的。
(???为什么要重编译???)
(???谁在执行重编译这一步???重编译的标准是什么???)
编译→优化
很多情况,我们的一条sql语句可能会反复执行,或者每次执行的时候只有个别的值不同
(比如query的where子句值不同,update的set子句值不同,insert的values值不同)
public DataSet GetList(string userId)
{
string sql =
@"SELECT
*
FROM dbo.User
WHERE UserId = @user_id ";
SqlParameter[] parameters =
{
new SqlParameter("@user_id", SqlDbType.NVarChar ),
};
parameters[0].Value = userId;
return DbHelperSQL.Query(sql, parameters);
}
如果每次都需要经过, 则效率就明显不行了。
(???除了这些SQL语句,其它语句是怎么优化的???)
将编译封装→预编译
想到一句话:没有什么是包一层不能解决问题的,如果有,那就再包一层...
计算机科学中的每个问题都可以用一间接层解决
出自《游戏引擎架构》滑铁卢大学教授 Jay Black
译者 miloyip 在备注中指明此句应沿自 David John Wheeler。
英文原句是"All problems in computer science can be solved by another level of indirection."
其实就是分层的思想,计算机网络也分了7层,
编译器就是你的 c++/java/python 和机器之间的中间层
(???分层的其它应用???)
这次包的这一层叫→预编译
预编译语句就是将这类语句中的值用占位符替代,可以视为将sql语句模板化或者说参数化。
一次编译、多次运行,省去了解析优化等过程。
解析优化
=>预编译
=>编译
预编译.缓存
预编译语句被DB的编译器编译后的执行代码被缓存下来,
那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中(相当于一个涵数)就会得到执行。
并不是所以预编译语句都一定会被缓存,数据库本身会用一种策略(内部机制)。
预编译.实现方法
预编译是通过PreparedStatement和占位符来实现的。
预编译作用
优化SQL语句的执行
预编译之后的 sql 多数情况下可以直接执行,DBMS不需要再次编译,
越复杂的sql,编译的复杂度将越大,预编译阶段可以合并多次操作为一个操作。
可以提升性能。
防止SQL注入
使用预编译,而其后注入的参数将不会再进行SQL编译。
也就是说其后注入进来的参数系统将不会认为它会是一条SQL语句,而默认其是一个参数,
参数中的or或者and等就不是SQL语法保留字了。
用户名:"123"
密码:"123"
[登录]
select * from users where username='123' and password='123'
用户名:"123' or 1=1 --"
密码:"任何输入"
[登录]
select * from users where username='123' or 1=1 --' and password='任何输入'
预编译作用 = 效率 + 安全
预编译可以完全杜绝注入攻击吗?
使用sql数据库预编译,理论上可以杜绝sql注入攻击,但是也会有例外。
很久之前ThinkPHP5
曾有一个SQL注入漏洞。该漏洞简单来说,就是在预编译阶段即prepare阶段,sql语句的模板中参数名可控,导致的sql注入。
具体的可以参见这篇文章→https://www.leavesongs.com/penetration/thinkphp5-in-sqlinjection.html
这次并不是通过参数注入payload,而是在sql模板生成时在参数名处拼接payload,在prepare阶段进行注入攻击。
虽然在prepare阶段可以注入payload,但是这样的sql模板会引起mysql数据库的报错从而无法顺利执行到execute阶段。
然而在prepare阶段,仍然是可以执行部分payload的.
可见.prepare阶段的sql模板如果可控,仍然是有注入风险的
作者:嘻洋洋
链接:https://www.jianshu.com/p/9972d7b33061
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。