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

问题描述 投票:19回答:3

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

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似乎支持参数化查询,但不支持预处理语句(例如,MySQLdb的here以及sqlalchemy组中的TODO list for postgres driversthis answer)。实际上,有一个gist实现了一个psycopg2游标支持预备语句和一个关于它的最小explanation。还有一个suggestion子类化psycopg2中的游标对象,以手动提供准备好的语句。

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

  • 准备好的语句和参数化查询之间是否存在有意义的区别?这在实践中是否重要?如果使用参数化查询,是否需要担心预处理语句?
  • 如果存在差异,Python生态系统中预准备语句的当前状态是什么?哪些数据库适配器支持预处理语
python database prepared-statement sql-injection parameterized-query
3个回答
16
投票
  • 准备语句:对数据库上的预解释查询例程的引用,准备接受参数
  • 参数化查询:由您的代码进行的查询,使您将值与一些具有占位符值的SQL一起传递,通常是?%s或类似的东西。

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

例如:SQLite3库的C接口有很多用于处理prepared statement objects的工具,但是Python api几乎没有提到它们。您无法随时准备声明并多次使用。相反,您可以使用带有SQL代码的sqlite3.executemany(sql, params),在内部创建预准备语句,然后在循环中使用该语句来处理您给出的可迭代中的每个参数元组。

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

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

*例如,通过生成在生成器函数中输入DB的数据,然后使用executemany()将其全部从生成器插入,而不是每次循环时调用execute()

tl;博士

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

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

此外,如果您想要挑选,参数化查询并不总是使用准备好的声明;他们应尽可能这样做,但有时它只是在参数值中格式化。这里“准备语句”和“参数化查询”之间的真正区别实际上就是您使用的API的形状。


3
投票

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

我不确定,如果我是提供权威答案的人,但我会尝试解释我对情况的理解。

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

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

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

参数化查询似乎不太经常使用相同的别名(24 mil谷歌命中参数化语句,14 mil命中参数化查询)。有些人可能会将此术语用于其他目的。

准备好的陈述的好处是:

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

玩家在执行SQL查询

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

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

从应用程序的角度来看,如果代码确实在数据库服务器上使用预准备语句,并不是因为任何参与者可能缺乏对预准备语句的支持,那么要知道这一点并不容易。

结论

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

决定,如果准备好的声明值得出于性能原因。如果你有简单的SQL查询,它只执行几次,它将无济于事,有时它甚至会减慢执行速度。

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

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

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


0
投票

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

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.