跳至主要內容

使用 prepared statement

Klustron大约 5 分钟

使用 prepared statement

Prepared statement 是当前主流关系数据库都支持的一个功能,Klustron(昆仑数据库)系统也支持此功能。

使用 prepared statement 的好处主要有两点,首先可以大大提升信息系统的安全性,防止 SQL 注入;还可以在一定程度提升查询性能。

所以强烈建议使用 prepared statement 功能。本文不在此赘述prepared statement 的SQL语法或者 具体编程和 API 用法,可以参考PostgreSQL和MySQL 数据库的 client API 的文档。下面详细讲解 prepared statement 功能的这两个巨大的优势的工作原理。

Prepared statement 为什么可以阻止 SQL 注入

SQL 注入的原理

当前大多数信息系统都是用一个软件或者网页作为前端接收用户输入,然后在服务器的应用层(也就是使用 java,php,python,CGI 等开发的服务器应用软件系统)使用这些输入,实现应用逻辑,最终使用用户输入作为参数操作后台数据库做数据的增删改查。

很多应用开发者直接把前端用户的输入字符串作为 SQL 查询的一部分,拼接成一个 SQL 语句,这是一种非常差的编程方式。假如有一个系统,它的前端做一个删除产品的功能,它使用一个文本框接收用户输入一个产品编号,然后后端使用这个编号作为参数来删除产品。拼接 SQL 字符串的伪代码是

SQLstr = 'delete from products where pcode=\'' + textbox.string_value + '\'';

此时如果用户在前端输入这样一个字符串 : xx'‘ or '1,那么最终送给数据库的 SQL 语句就是

delete from products where pcode='xx' or '1';

那么这个语句就会把系统 products 表的所有数据都删除。

通常这类问题中,如果用户输入参数作为数值等非字符串参数值使用,那么有一定编程经验的开发者会把用户输入转换为数值等所需类型的常量然后再做拼接,这样就可以避免上述注入。以上面的查询为例,如果 pcode 列是 int 类型,那么用户的这个输入: xx‘ or ‘1

这个字符串经过数值转换,转为 0,然后应用再做 SQL 语句拼接,最终送给后端的 SQL 语句就是 delete from products where pcode=0

这样虽然可能会误删另一行,但是至少不会丢失全表数据。但是一些小白开发者连上面的数值类型转换都不做,那么就无法在这种简单情况下避坑了。

而如果上例的 pcode 确实就是字符串类型,现在有一些应用层的工具和开发范例来避免 SQL 注入,比如禁止各种运算符字符(比如 +=-/|& 等)或者禁止某些 SQL 关键字(比如 select, or,and 等),但是无法完全杜绝 SQL 注入。

可以说,只要用户输入的字符串仍然会直接被关系数据库的语法解析器来解析,那么 SQL 注入就一定是可以实现的。换个通俗点的说法:只要你还在应用层直接使用用用户输入的字符串(部分或者全部)来拼接 SQL 语句,那么 SQL 注入就一定会发生。

那么如何一劳永逸的避免 SQL 注入呢?这就要使用 prepared statement。

Prepared statement 的工作原理

以上面的 SQL 语句为例,prepared statement 就是 delete from products where pcode=?;

关系数据库的 SQL 解析器解析和优化这个语句,形成一个查询计划。在客户端用户程序得到的是这个 prepared 查询语句的一个句柄(handle),在 session 有效期内有效。这个查询计划就是执行这个查询语句的方法,但是在没有得到具体的参数之前,这个查询计划是无法执行的。

当用户通过其句柄(handle)绑定(bind)参数给这个 prepared statement 之后,用户就可以执行这个查询计划了。此时无论用户绑定什么样的参数,这个参数并不会被 SQL 解析器再次解析,它只是作为指定类型的参数值被这个查询计划使用来完成查询执行。所以,即时用户绑定的参数是 xx'‘or '1

那么最终相当于执行的 SQL 语句是 delete from products where pcode=‘'xx\' or \'1';

这样就不会有任何数据损失的危险。

并且,用户可以多次反复绑定不同的参数给这个 prepared statement,这样,假如这个结构的 SQL 语句数量很大的话,数据库系统就避免了大量地解析和优化同一结构的 SQL 语句,从而提升系统性能。

Klustron prepared statement 的语法

Klustron支持使用PostgreSQL 或者MySQL语法来定义prepared statement,不论是在 PostgreSQL连接中还是MySQL连接中。并且任何一种语法定义的prepared statement都可以使用PostgreSQL或者MySQL的客户端 API 来绑定参数然后执行。

Klustron不支持使用MySQL 的EXECUTE 命令 来执行prepared statement,主要是因为MySQL的EXECUTE 命令依赖于用户定义的变量这种功能,这是klustron不支持的。但是可以使用PostgreSQL的EXECUTE 语法来执行prepared statement,无论是在PostgreSQL连接中还是MySQL连接中,也无论是使用PostgreSQL 还是MySQL语法来PREPARE 定义的。

示例:在PostgreSQL或者MySQL连接中都可以执行下列语句

  1. PostgreSQL 语法的PREPARE 和 EXECUTE

PREPARE fooplan (int, text, bool, numeric) AS INSERT INTO foo VALUES($1, $2, $3, $4); EXECUTE fooplan(1, 'Hunter Valley', 't', 200.00);

  1. MySQL语法的PREPARE 和 PostgreSQL 语法的 EXECUTE

PREPARE get_orders from 'SELECT * from orders where id=?'; EXECUTE get_orders(123);

END