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

Confusion between prepared statement and parameterized query in Python

据我了解,准备好的语句(主要)是一种数据库功能,可让您将参数与使用此类参数的代码分开。示例:

PREPARE fooplan (int, text, bool, numeric) AS
    INSERT INTO foo VALUES(, , , );
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])

现在看来,prepared statements 大部分用在数据库语言中,而参数化查询主要用在连接数据库的编程语言中,虽然我看到了这个规则的例外。

问题是询问准备语句和参数化查询之间的区别会带来很多困惑。诚然,他们的目的是相同的,但他们的方法似乎截然不同。然而,在 psycopg2 中有 sources indicating that both are the same. MySQLdb and Psycopg2 seem to support parameterized queries but don’t support prepared statements (e.g. here for MySQLdb and in the TODO list for postgres drivers or this answer in the sqlalchemy group). Actually, there is a gist implementing a psycopg2 cursor supporting prepared statements and a minimal explanation about it. There is also a suggestion 对游标对象进行子类化以手动提供准备好的语句。

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

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

准备好的语句是已经解释过的语句,DBMS 更改参数并立即开始查询。这是某些DBMS的特性,可以实现快速响应(媲美存储过程)。

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

如果你测量执行相同查询 3-4 次的时间(select 不同的条件)你会看到参数化查询的时间相同,第二次执行准备好的语句(无论如何,DBMS 第一次必须解释脚本。

首先,您的问题表明准备充分 - 干得好。

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

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

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

Parametrized statement通常是prepared statement的别名,prepared statement有 一些参数。

参数化查询 似乎较少使用相同的别名(2400 万 Google 点击 参数化语句,1400 万次参数化查询)。有可能,有些人使用 此术语用于其他目的。

预处理语句的优点是:

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

正在执行 SQL 查询的玩家

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

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

从应用的角度很难知道,代码是否真的会使用prepared 是否在数据库服务器上声明,因为 任何参与者都可能缺乏准备好的支持 陈述.

结论

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

决定准备语句是否出于性能原因值得。如果您有简单的 SQL 查询, 只执行几次,这无济于事,有时甚至会减慢执行速度 位.

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

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

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

  • Prepared statement:对数据库中预解释查询例程的引用,准备好接受参数

  • 参数化查询:您的代码以这样一种方式进行的查询,即您在 alongside 一些具有占位符值的 SQL 中传递值,通常是 ?%s 或类似的东西。

这里的混淆似乎源于(明显)缺乏直接获取准备好的语句对象的能力和将值传递到行为非常相似的 'parametrized query' 方法的能力之间的区别。 .. 因为它是一个,或者至少为你制作了一个。

例如:SQLite3 库的 C 接口有很多可以使用的工具 prepared statement objects, but the Python api 几乎没有提到它们。您不能准备一个语句并在需要时多次使用它。相反,您可以使用 sqlite3.executemany(sql, params),它采用 SQL 代码,在内部 创建准备好的语句 ,然后在循环中使用该语句来处理每个参数元组在你给出的迭代中。

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

也许更重要的是,准备好的语句和参数化查询使您的数据保持卫生并与 SQL 代码分开。 这比字符串格式化更可取!您应该将参数化查询和准备好的语句以一种或另一种形式视为从您的应用程序传递变量数据的唯一方法进入数据库。如果您尝试以其他方式构建 SQL 语句,它不仅 运行 会显着变慢,而且您将容易受到 other problems.

的攻击

*例如,通过在生成器函数中生成要馈入数据库的数据,然后使用 executemany() 从生成器中一次性插入所有数据,而不是调用 execute()每次循环。

tl;博士

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

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

此外,如果您想挑剔,参数化查询不会总是在幕后使用准备好的语句;他们应该尽可能这样做,但有时它只是在参数值中格式化。 'prepared statement' 和 'parametrized query' 之间的真正区别实际上只是您使用的 API 的形状。

我认为关于使用 executemany 的评论无法处理应用程序将数据超时存储到数据库中并希望每个插入语句尽可能高效的情况。因此希望准备一次插入语句和 re-use 准备好的语句。 或者,可以将所需的语句放入存储过程中,然后 re-use 它。