Python中预准备语句和参数化查询之间的混淆

时间:2016-04-01 23:47:41

标签: python database prepared-statement sql-injection parameterized-query

据我了解,预备语句(主要)是一个数据库功能,允许您将参数与使用此类参数的代码分开。例如:

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

参数化查询替代手动字符串插值,因此不是执行

cursor.execute("SELECT FROM tablename WHERE fieldname = %s" % value)

我们可以做到

cursor.execute("SELECT FROM tablename WHERE fieldname = %s", [value])

现在,似乎准备好的语句大部分都用在数据库语言中,参数化查询主要用于连接数据库的编程语言,尽管我已经看到了这个规则的例外。

问题在于询问预准备语句和参数化查询之间的区别会带来很多混乱。他们的目的无疑是相同的,但他们的方法似乎是截然不同的。然而,有sources表明两者是相同的。 MySQLdb和Psycopg2似乎支持参数化查询,但不支持预处理语句(例如MySQL here和sqlalchemy组中的TODO list for postgres driversthis answer。实际上,有一个gist实现了一个支持预处理语句的psycopg2游标和一个关于它的最小explanation。在psycopg2中还有一个suggestion子类化游标对象,以手动提供准备好的语句。

我希望得到以下问题的权威答案:

  • 预准备语句和参数化查询之间是否存在有意义的区别?这在实践中是否重要?如果使用参数化查询,是否需要担心预处理语句?

  • 如果存在差异,Python生态系统中预准备语句的当前状态是什么?哪些数据库适配器支持预准备语句?

4 个答案:

答案 0 :(得分:16)

  • 准备语句:对数据库上的预解释查询例程的引用,准备接受参数

  • 参数化查询:由您的代码进行的查询,使您在中传递值一些具有占位符值的SQL,通常为?或{{1或者那种味道。

这里的混淆似乎源于(明显)直接获得准备好的陈述对象的能力与将值传递给参数化查询的能力之间的区别。非常像一个人的方法...因为它是一个,或者至少为你做一个。

例如:SQLite3库的C接口有很多工具可以使用prepared statement objects,但Python api几乎没有提及它们。您无法随时准备语句并多次使用。相反,您可以使用%s来获取SQL代码,在内部创建一个准备好的语句 ,然后在循环中使用该语句来处理您给出的可迭代中的每个参数元组。 / p>

Python中的许多其他SQL库的行为方式相同。使用准备好的语句对象可能是一个真正的痛苦,并且可能导致歧义,并且在像Python这样的语言中,它非常倾向于清晰和轻松地执行原始执行速度,它们并不是最好的选择。从本质上讲,如果您发现自己必须对复杂的SQL查询进行数十万或数百万次调用,并且每次都要重新解释,那么您应该采取不同的方式。无论如何,有时人们希望他们可以直接访问这些对象,因为如果在数据库服务器周围保留相同的预处理语句,则不必一遍又一遍地解释相同的SQL代码;大多数情况下,这将从错误的方向接近问题,您将在其他地方或通过重组代码获得更大的节省。*

一般来说,更重要的是准备语句和参数化查询使您的数据保持卫生并与SQL代码分离的方式。 这比字符串格式化要好得多!您应该以一种或另一种形式将参数化查询和预处理语句视为将可变数据从应用程序传递到数据库的唯一方法。如果您尝试构建SQL语句,否则它将不仅运行速度明显变慢,而且您将容易受到other problems的攻击。<​​/ p>

*例如,通过生成将在生成器函数中提供给DB的数据,然后使用sqlite3.executemany(sql, params)将所有数据从生成器中一次插入,而不是调用executemany()每次循环。

TL;博士

参数化查询是一个单一操作,它在内部生成预准备语句,然后传入参数并执行。

编辑:很多人都看到了这个答案!我还想澄清一下,许多数据库引擎也有一个预准备语句的概念,可以用明文查询语法显式构造,然后在客户端会话的生命周期中重用(例如在postgres中)。有时您可以控制是否缓存查询计划以节省更多时间。有些框架会自动使用这些框架(我已经看过rails和ORM积极地使用它们),有时候,当有正在准备的查询的表单排列时,有时会对它们造成不利影响。

此外,如果你想挑选,参数化的查询不会总是使用一个准备好的声明;他们应尽可能这样做,但有时它只是在参数值中格式化。 &#39;准​​备好的陈述与#39;之间的真正区别和&#39;参数化查询&#39;这里只是您使用的API的形状。

答案 1 :(得分:3)

首先,你的问题显示出非常好的准备 - 干得好。

我不确定,如果我是提供权威答案的人,但我会尽力解释我的 了解情况。

预备语句是一个对象,由于PREPARE而在数据库服务器端创建 声明,将提供的SQL语句转换为带参数的临时过程。准备 statement具有当前数据库会话的生命周期,并在会话结束后被丢弃。 SQL语句DEALOCATE允许显式销毁准备好的语句。

数据库客户端可以使用SQL语句EXECUTE通过调用它来执行准备好的语句 名称和参数。

参数化语句是预准备语句的别名,通常是预准备语句具有 一些参数。

参数化查询似乎不常使用相同的别名(24万谷歌的点击率) 参数化语句,参数化查询的14 mil命中。有些人可能会使用它 这个词用于其他目的。

准备好的陈述的好处是:

  • 更快地执行实际准备好的语句调用(不计算PREPARE
  • 的时间
  • 对SQL注入攻击的依赖性

执行SQL查询的玩家

真实应用可能会有以下参与者:

  • 应用程序代码
  • ORM包(例如sqlalchemy)
  • 数据库驱动程序
  • 数据库服务器

从应用程序的角度来看,如果代码真的使用准备就不容易知道 数据库服务器上的声明与否?任何参与者可能缺乏准备的支持 语句

结论

在应用程序代码中阻止SQL查询的直接整形,因为它容易受到SQL注入攻击。对于 因此,建议使用ORM为参数化查询提供的任何内容,即使它确实如此 不会导致在数据库服务器端使用预准备语句,因为可以优化ORM代码 防止这种攻击。

决定,如果准备好的声明因性能原因而值得。如果你有简单的SQL查询, 只执行几次,它不会有帮助,有时它甚至会减慢执行速度a 位。

对于多次执行的复杂查询,执行时间相对较短 效果最大。在这种情况下,您可以按照以下步骤操作:

  • 检查您要使用的数据库是否支持PREPARE语句。在大多数情况下 将出席。
  • 检查您使用的驱动器是否支持预备语句,如果没有,请尝试查找另一个 一个支持它。
  • 在ORM包级别检查此功能的支持。有时它会因驾驶员而改变驾驶员(例如 由于MySQL的管理方式,sqlalchemy声明对MySQL准备语句的一些限制 那)。

如果您正在寻找真正权威的答案,我会前往sqlalchemy的作者。

答案 2 :(得分:0)

无法立即执行sql语句:DBMS必须在执行之前解释它们。

预处理语句已经解释了语句,DBMS更改参数和查询立即启动。这是某些DBMS的一个功能,您可以实现快速响应(与存储过程相当)。

参数化语句只是您编写编程语言中查询字符串的一种方式。由于sql字符串的形成方式无关紧要,因此DBMS的响应速度较慢。

如果您测量执行3-4次相同查询的时间(使用不同条件选择),您将看到参数化查询的相同时间,从第二次执行预准备语句开始的时间较短(DBMS第一次执行无论如何解释剧本。)

答案 3 :(得分:0)

我认为,有关使用executemany的注释无法解决应用程序OVER TIME将数据存储到数据库中并希望每个插入语句尽可能高效的情况。因此,需要一次准备插入语句并重新使用准备好的语句。 或者,可以将所需的语句放入存储的proc中并重新使用。