【问题标题】:How PreparedStatement Works Internally in Java: How compilation & caching of SQL worksPreparedStatement 如何在 Java 内部工作:SQL 的编译和缓存如何工作
【发布时间】:2017-06-05 03:13:51
【问题描述】:

我在互联网上读到它,但我无法得到一些问题的答案。

Q1. 据说在执行 PreparedStatement 时,DBMS 可以直接运行 PreparedStatement SQL 语句,无需先编译。我的问题是当preparedStetement.execute() 被调用时会发生什么? (我的理解:没有任何参数的sql被发送到DBMS,由DBMS编译和缓存以备将来使用。然后发送参数&DBMS用占位符替换它们并执行。)

Q2. 如果下次我使用相同的 sql 执行 PreparedStetement.execute() 会发生什么? (我的理解:DBMS将sql与之前的sql进行比较,如果匹配则取编译的sql,替换参数并执行。)

Q3.如果我偶尔调用数据库,那么准备好的语句将无法帮助我提高性能,因为在此期间数据库缓存将被清除。所以每次都会编译sql。对吧?

【问题讨论】:

  • 您的问题在这里有一些答案。 stackoverflow.com/questions/687550/…
  • 内部行为高度依赖于 JDBC 驱动程序和 DBMS。在不知道的情况下无法正确回答
  • 您要问什么,正如其他 cmets 所说,取决于 DBMS。不过,对于像 Oracle 这样的 DBMS,它基本上是正确的。然而,除了性能之外,prepared statement 的主要原因通常是相关的安全性,以避免 sql 注入

标签: java sql database jdbc prepared-statement


【解决方案1】:

preparedStatement 需要 DBMS 的支持。

如果sql已经编译好了,DB会缓存它。当再次出现相同的时候,只需发送参数即可完成缓存的sql。

prepareStatement具有三个优点:

  1. 使代码更清晰,以便您阅读。

  2. 尽可能提高性能。减少编译时间。

  3. 最重要的是,它使 sql 更加安全。如果你的sql如下:

    String sql = "select * from users where userid = " + userid; // use statement
    

    有人给它一个用户ID值,比如

    userid = "1;delete users;";
    

    语句将执行 sql 为

    "select * from users where userid=1;"
    "delete users;"
    

如果操作者真的有权这样做,这对数据库来说是非常危险的操作。

如果我们使用preparestatement

String sql = "select * from users where userid = ?"; // use preparestatement

数据库会将sql编译为"select * from users where userid = '?'",等待参数"?"表示sql会这样执行

"select * from users where userid = '1;delete users;'  ;" // of course, it will select 0 column. 

将参数视为字符串。 这是接口java.sql.Connection 类中的注释。阅读它

/**
 * Creates a <code>PreparedStatement</code> object for sending
 * parameterized SQL statements to the database.
 * <P>
 * A SQL statement with or without IN parameters can be
 * pre-compiled and stored in a <code>PreparedStatement</code> object. This
 * object can then be used to efficiently execute this statement
 * multiple times.
 *
 * <P><B>Note:</B> This method is optimized for handling
 * parametric SQL statements that benefit from precompilation. If
 * the driver supports precompilation,
 * the method <code>prepareStatement</code> will send
 * the statement to the database for precompilation. Some drivers
 * may not support precompilation. In this case, the statement may
 * not be sent to the database until the <code>PreparedStatement</code>
 * object is executed.  This has no direct effect on users; however, it does
 * affect which methods throw certain <code>SQLException</code> objects.
 * <P>
 * Result sets created using the returned <code>PreparedStatement</code>
 * object will by default be type <code>TYPE_FORWARD_ONLY</code>
 * and have a concurrency level of <code>CONCUR_READ_ONLY</code>.
 * The holdability of the created result sets can be determined by
 * calling {@link #getHoldability}.
 *
 * @param sql an SQL statement that may contain one or more '?' IN
 * parameter placeholders
 * @return a new default <code>PreparedStatement</code> object containing the
 * pre-compiled SQL statement
 * @exception SQLException if a database access error occurs
 * or this method is called on a closed connection
 */
PreparedStatement prepareStatement(String sql)
    throws SQLException;

【讨论】:

  • 你的例子生成的sql应该是"select * from users where userid=1=1;""delete users;"
  • 我知道sql注入部分。实际上,我在使用它时遇到了一些性能问题,我使用 sendStringParametersAsUnicode=false 解决了它。现在我正试图更深入地了解下划线正在发生的事情。不过还是谢谢你的解释:)
  • 假设我正在编写 PreparedStatement ps = connection.prepare("SOME SQL"); sql 编译是发生在 connection.prepare("SOME SQL")` 中还是发生在 DBMS 中还是取决于 jdbc 驱动程序
  • @SumitPal 视情况而定
  • 请注意,对于许多数据库系统而言,准备好的语句实际上并不是这样工作的:执行时使用的值与带有占位符的查询分开发送。
【解决方案2】:

java.sql.PreparedStatement 是 JRE 定义的 API。

execute() 的实际内部行为取决于 DBMS 供应商提供的 JDBC 驱动程序实现。

例如,在某些嵌入式数据库(SQLite、H2)中,sending statement to server 没有任何意义。

您应该查阅您正在使用的 JDBC 驱动程序的文档。

【讨论】:

  • 我一定会仔细阅读文档。但是你知道在 SQL Server 的情况下会发生什么吗(如果你已经知道的话)。
  • 假设我正在编写 PreparedStatement ps = connection.prepare("SOME SQL"); sql 编译是发生在 connection.prepare("SOME SQL")` 中还是发生在 DBMS 中还是取决于 jdbc 驱动程序
  • @SumitPal :你为什么不直接阅读文档? msdn.microsoft.com/en-us/library/ms378754(v=sql.110).aspx
【解决方案3】:

除了上面 rkosegi 的回答之外,我想更多地描述一下它是如何与 PostgreSQL 驱动程序一起工作的,因为它突出了这里的一些困难。

使用 PostgreSQL 驱动程序(请参阅documentation),行为实际上是可配置的并且相当复杂。

PostgreSQL 查询协议允许单独发送查询和参数,因此 PreparedStatement API不需要通过要求数据库准备语句然后稍后执行的过程。权衡是在初始准备中存在额外的服务器端准备开销以及何时重用查询计划的问题,但这与较短的查询重用启动时间相抵消。因此,如果您正在优化并且重复查找要准备的单个记录,而如果您在可变范围内运行或不重用语句,那么您可能不会。这是在数据库连接上配置的。这通常设置为关于在一定数量的重复使用后何时开始缓存计划的阈值。

当不使用服务器端准备时,驱动程序在同一命令中分别向数据库发送参数化查询和参数。然后,服务器解析查询,将变量插入适当的位置并进行计划。然后计划在运行后被丢弃。

然后使用服务器端准备,但是它可以按照您的描述工作。

所以这是一个很难的例子,说明为什么你不能提出问题并得到明确的答案。 JDBC 提供了一个编程接口,驱动程序提供了它如何工作的详细信息。 prepareStatement 没有定义查询计划是否被缓存。这是司机的决定。

【讨论】:

  • 假设我正在写 PreparedStatement ps = connection.prepare("SOME SQL"); Is the sql compilation happening in connection.prepare("SOME SQL")` 或者它发生在 DBMS 中或者它取决于 jdbc 驱动程序。
  • 好吧,如果您将 jdbc 驱动程序用于 csv 文件,我会假设它会发生在 jdbc 驱动程序中。所以是的,取决于驱动程序。
猜你喜欢
  • 1970-01-01
  • 2010-09-29
  • 1970-01-01
  • 2012-08-21
  • 1970-01-01
  • 2015-12-24
  • 2014-11-22
  • 1970-01-01
  • 2011-04-25
相关资源
最近更新 更多