PreparedStatement真的比Statement快吗?

本文通过实验对比了PreparedStatement与Statement在不同场景下的性能表现,探讨了两者在执行效率上的差异。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关键字: 企业应用   jdbc    

从开始写java程序就一直被灌输着一种思想,如果使用jdbc一定要使用PreparedStatement,而不要使用Statement对象。
其中的原因有好多,比如可以防止SQL注入攻击,防止数据库缓冲池溢出,代码的可读性,可维护性。这些都很正确。
但是还有一点人们经常提的就是PreparedStatement能够显著的提高执行效率。
看了两篇关于PreparedStatement和Statement的帖子
http://dev2dev.bea.com.cn/bbs/thread.jspa?forumID=121&threadID=10397&start=0&tstart=0
http://www.iteye.com/topic/5631?page=1
里面提到,PreparedStatement并不一定比Statement快,于是想自己动手测试一下
一共分5种情况测试

代码
  1. stmt      pre-stmt     
  2. --1:每次都单独连数据库--100条   
  3. 21.936  22.076  
  4. 22.05     22.051       
  5. 22.172  22.178     
  6. --2:Stmt使用相同的SQL语句,参数值也相同--100条   
  7. 0.78      0.796    
  8. 0.718     0.749    
  9. 0.749     0.764    
  10. --3:Stmt使用相同的SQL语句,参数值不同--100条   
  11. 0.967     0.749    
  12. 0.78      0.874    
  13. 0.749     0.749    
  14. --4:Stmt使用相同的SQL语句,参数值也相同--10000条   
  15. 33.079  36.154     
  16. 34.156  39.306     
  17. 34.359  36.138     
  18. --5:Stmt使用相同的SQL语句,参数值不同--10000条   
  19. 32.799  34.125     
  20. 32.564  35.53      
  21. 35.701  34.952     
  22. 32.582  40.798     
  23. 38.893  42.345     
  24. 35.082  41.736     
<script>render_code();</script>
分析:
第一种情况:
由于我测试时采用jdbc直接链接数据库,所以在第一种情况下插入速度很慢,时间都消耗在了数据库链接的创建上了,符合常理,没什么疑义。
第二种情况:
由于使用Statement插入时sql语句都相同,我通过阅读上面的两篇帖子,猜想可能是Statement语句也被DB缓存了,所以跟
PreparedStatement速度差不多,我想如果每次插入的sql都不同,那么Statement应该慢下来了吧。
第三种情况:
并非像我在执行第二种情况时想象的那样,PreparedStatement还是跟Statement差不多,于是我想是不是数据量不够大呀,于是我增大了
插入数据量,希望PreparedStatement能够比Statement快些
第四/五种情况:
测试结果PreparedStatement反到更慢了。

PreparedStatement什么情况下才会比Statement快?那位大侠来指点一下!

代码如下:
DDL
代码
  1. CREATE TABLE dbo.jdbc_stmt    
  2. (   
  3.     id   numeric(10,0) IDENTITY,   
  4.     name varchar(10)   NOT NULL,   
  5.     grad int           NOT NULL,   
  6.     age  int           NOT NULL,   
  7.     dept int           NOT NULL   
  8. )   
<script>render_code();</script>
测试程序
代码
  1. public void jdbcStmtPerformance()   
  2.     {   
  3.         //1:每次都单独连数据库--100条   
  4. //      String stmtSQL = "insert into jdbc_stmt values('ivanl',2,25,1001)";   
  5. //      for(int i = 0; i< 100; i++)   
  6. //      {   
  7. //          logger.debug("stmt #"+i);   
  8. //          getJdbcTemplate().update(stmtSQL);   
  9. //      }   
  10.            
  11.         //2:Stmt使用相同的SQL语句,参数值也相同--100条   
  12. //      getJdbcTemplate().execute(new StatementCallback(){   
  13. //          public Object doInStatement(Statement stmt) throws SQLException   
  14. //          {   
  15. //              String stmtSQL = "insert into jdbc_stmt values('ivanl',2,25,1001)";   
  16. //              for(int i = 0; i< 100; i++)   
  17. //              {   
  18. //                  logger.debug("stmt #"+i);   
  19. //                  stmt.executeUpdate(stmtSQL);   
  20. //              }   
  21. //              return null;   
  22. //          }   
  23. //      });   
  24.            
  25.         //3:Stmt使用相同的SQL语句,参数值不同--100条   
  26. //      getJdbcTemplate().execute(new StatementCallback(){   
  27. //          public Object doInStatement(Statement stmt) throws SQLException   
  28. //          {   
  29. //              String stmtSQL = "insert into jdbc_stmt values('ivanl',2,25,";   
  30. //              for(int i = 0; i< 100; i++)   
  31. //              {   
  32. //                  logger.debug("stmt #"+i);   
  33. //                  stmt.executeUpdate(stmtSQL+i+")");   
  34. //              }   
  35. //              return null;   
  36. //          }   
  37. //      });   
  38.            
  39. //      4:Stmt使用相同的SQL语句,参数值也相同--10000条   
  40. //      getJdbcTemplate().execute(new StatementCallback(){   
  41. //          public Object doInStatement(Statement stmt) throws SQLException   
  42. //          {   
  43. //              String stmtSQL = "insert into jdbc_stmt values('ivanl',2,25,1001)";   
  44. //              for(int i = 0; i< 10000; i++)   
  45. //              {   
  46. //                  logger.debug("stmt #"+i);   
  47. //                  stmt.executeUpdate(stmtSQL);   
  48. //              }   
  49. //              return null;   
  50. //          }   
  51. //      });   
  52.            
  53. //      5:Stmt使用相同的SQL语句,参数值不同--10000条   
  54.         getJdbcTemplate().execute(new StatementCallback(){   
  55.             public Object doInStatement(Statement stmt) throws SQLException   
  56.             {   
  57.                 String stmtSQL = "insert into jdbc_stmt values('ivanl',2,25,";   
  58.                 for(int i = 0; i< 10000; i++)   
  59.                 {   
  60.                     logger.debug("stmt #"+i);   
  61.                     stmt.executeUpdate(stmtSQL+i+")");   
  62.                 }   
  63.                 return null;   
  64.             }   
  65.         });   
  66.     }   
  67.        
  68.        
  69.     public void jdbcPreStmtPerformance()   
  70.     {   
  71.         //1:每次都单独连数据库--100条   
  72. //      String stmtSQL = "insert into jdbc_stmt values(?,?,?,?)";   
  73. //      for(int i = 0; i< 100; i++)   
  74. //      {   
  75. //          logger.debug("pre-stmt #"+i);   
  76. //          getJdbcTemplate().update(stmtSQL, new Object[]{"ivanl", new Integer(2), new Integer(25), new Integer(1002)});   
  77. //      }   
  78.            
  79.         //2:Stmt使用相同的SQL语句,参数值也相同--100条   
  80. //      String stmtSQL = "insert into jdbc_stmt values(?,?,?,?)";   
  81. //      getJdbcTemplate().execute(stmtSQL,new PreparedStatementCallback(){   
  82. //          public Object doInPreparedStatement(PreparedStatement ps)  throws SQLException   
  83. //          {   
  84. //              for(int i = 0; i< 100; i++)   
  85. //              {   
  86. //                  logger.debug("pre-stmt #"+i);   
  87. //                  ps.setString(1, "ivanl");   
  88. //                  ps.setInt(2, 2);   
  89. //                  ps.setInt(3, 25);   
  90. //                  ps.setInt(4, 1002);   
  91. //                  ps.execute();   
  92. //              }   
  93. //              return null;   
  94. //          }   
  95. //      });   
  96.         //3:Stmt使用相同的SQL语句,参数值不同--100条   
  97. //      String stmtSQL = "insert into jdbc_stmt values(?,?,?,?)";   
  98. //      getJdbcTemplate().execute(stmtSQL,new PreparedStatementCallback(){   
  99. //          public Object doInPreparedStatement(PreparedStatement ps)  throws SQLException   
  100. //          {   
  101. //              for(int i = 0; i< 100; i++)   
  102. //              {   
  103. //                  logger.debug("pre-stmt #"+i);   
  104. //                  ps.setString(1, "ivanl");   
  105. //                  ps.setInt(2, 2);   
  106. //                  ps.setInt(3, 25);   
  107. //                  ps.setInt(4, i);   
  108. //                  ps.execute();   
  109. //              }   
  110. //              return null;   
  111. //          }   
  112. //      });   
  113.         //4:Stmt使用相同的SQL语句,参数值也相同--10000条   
  114. //      String stmtSQL = "insert into jdbc_stmt values(?,?,?,?)";   
  115. //      getJdbcTemplate().execute(stmtSQL,new PreparedStatementCallback(){   
  116. //      public Object doInPreparedStatement(PreparedStatement ps)  throws SQLException   
  117. //      {   
  118. //          for(int i = 0; i< 10000; i++)   
  119. //          {   
  120. //              logger.debug("pre-stmt #"+i);   
  121. //              ps.setString(1, "ivanl");   
  122. //              ps.setInt(2, 2);   
  123. //              ps.setInt(3, 25);   
  124. //              ps.setInt(4, 1002);   
  125. //              ps.execute();   
  126. //          }   
  127. //          return null;   
  128. //      }   
  129. //  });   
  130.         //5:Stmt使用相同的SQL语句,参数值不同--10000条   
  131.         String stmtSQL = "insert into jdbc_stmt values(?,?,?,?)";   
  132.         getJdbcTemplate().execute(stmtSQL,new PreparedStatementCallback(){   
  133.             public Object doInPreparedStatement(PreparedStatement ps)  throws SQLException   
  134.             {   
  135.                 for(int i = 0; i< 10000; i++)   
  136.                 {   
  137.                     logger.debug("pre-stmt #"+i);   
  138.                     ps.setString(1"ivanl");   
  139.                     ps.setInt(22);   
  140.                     ps.setInt(325);   
  141.                     ps.setInt(4, i);   
  142.                     ps.execute();   
  143.                 }   
  144.                 return null;   
  145.             }   
  146.         });   
  147.            
  148.     }   
<think>嗯,用户问的是PreparedStatementStatement的区别,这个问题在数据库编程里挺常见的。首先得理清楚它们的基本概念,然后分点说明区别。可能用户正在学习JDBC或者数据库操作,想了解为什么推荐使用PreparedStatement。 首先,Statement是Java里执行SQL语句的基本接口,而PreparedStatement是它的子接口,增加了预编译和参数化查询的功能。需要强调预编译的好处,比如性能提升,因为SQL语句被数据库预编译后,多次执行时不需要重新解析和优化,尤其是处理批量操作的时候。 然后,SQL注入的问题。Statement拼接SQL字符串容易导致注入攻击,比如用户输入里如果有恶意代码,可能会被当作SQL的一部分执行。而PreparedStatement使用参数化查询,用户输入作为参数传递,不会被解析为SQL代码,这样更安全。需要举个例子说明,比如登录验证时的SQL语句。 接下来是可读性和维护性。PreparedStatement用占位符代替字符串拼接,代码更清晰,尤其是当SQL语句很长或者参数很多的时候。比如插入操作,用问号作为占位符,然后按位置设置参数,比在字符串里拼变量更不容易出错。 还有数据类型处理,PreparedStatement自动处理Java对象和SQL数据类型之间的转换,比如Date、Time这些,而Statement需要手动转成字符串,可能出错或者格式不对。 性能方面,除了预编译,还要提到数据库缓存执行计划的情况。同一个PreparedStatement多次执行时,数据库可能会重用之前的执行计划,减少开销。而Statement每次执行都要重新解析,即使SQL语句结构相同,只是参数不同。 可能用户还不太清楚预编译的具体过程,或者参数化查询如何防止注入,需要简单解释一下。比如,预编译时SQL结构已经固定,参数值是在编译后传入,不会被当作语法部分处理。 另外,使用场景的区别。Statement可能适用于静态SQL,比如DDL语句,而PreparedStatement适合动态参数多的DML操作。不过现在一般推荐总是用PreparedStatement,除非有特别原因不用。 最后,是否需要提到CallableStatement?可能暂时不用,用户的问题集中在PreparedStatementStatement的区别,不需要扩展其他接口。 总结的话,应该分点列出主要区别,包括预编译、性能、安全性、可读性、数据类型处理等方面,每个点简要说明。确保用户理解为什么在实际开发中更推荐使用PreparedStatement。</think>PreparedStatementStatement 是 Java JDBC 中用于执行 SQL 的接口,核心区别如下: --- ### 1. **预编译机制** - **PreparedStatement** SQL 语句会被**预编译**并存储在数据库中,后续只需传递参数值。 示例: ```java String sql = "SELECT * FROM users WHERE age > ?"; PreparedStatement pstmt = connection.prepareStatement(sql); pstmt.setInt(1, 18); // 参数下标从 1 开始 ``` 数据库会缓存编译后的执行计划,重复执行时效率更高。 - **Statement** 每次执行 SQL 都需要重新编译,效率较低: ```java Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery("SELECT * FROM users WHERE age > 18"); ``` --- ### 2. **SQL 注入防护** - **PreparedStatement** 通过**参数化查询**(`?` 占位符)天然防御 SQL 注入: ```java // 用户输入 name = "admin' OR '1'='1" String sql = "SELECT * FROM users WHERE name = ?"; pstmt.setString(1, name); // 输入会被转义为普通字符串,不会破坏 SQL 结构 ``` - **Statement** 直接拼接字符串存在注入风险: ```java String sql = "SELECT * FROM users WHERE name = '" + name + "'"; // 实际执行:SELECT ... WHERE name = 'admin' OR '1'='1'(返回所有数据) ``` --- ### 3. **性能对比** - **PreparedStatement** - 适合批量操作(如插入 1000 条数据): ```java for (int i = 0; i < 1000; i++) { pstmt.setString(1, "user" + i); pstmt.addBatch(); // 添加到批处理 } pstmt.executeBatch(); // 一次提交 ``` - 数据库可复用预编译的 SQL 模板。 - **Statement** 每次执行需重新解析 SQL,批量操作效率低。 --- ### 4. **数据类型处理** - **PreparedStatement** 自动处理数据类型转换(如 `setDate()`, `setBlob()`): ```java pstmt.setDate(1, new java.sql.Date(System.currentTimeMillis())); ``` - **Statement** 需手动拼接复杂类型(如日期、二进制数据),易出错: ```java String date = "2023-10-01"; String sql = "INSERT INTO logs VALUES ('" + date + "')"; // 依赖字符串格式 ``` --- ### 5. **可读性与维护性** - **PreparedStatement** 使用占位符 `?` 使 SQL 结构清晰,易于维护: ```sql UPDATE products SET price = ? WHERE id = ? ``` - **Statement** 长 SQL 拼接易混乱: ```java String sql = "UPDATE products SET price=" + price + " WHERE id=" + id; ``` --- ### 总结对比表 | 特性 | PreparedStatement | Statement | |---------------------|----------------------------------|------------------------| | **预编译** | ✔️ 支持 | ❌ 不支持 | | **防 SQL 注入** | ✔️ 天然防护 | ❌ 需手动处理 | | **性能** | ✔️ 高(尤其批量操作) | ❌ 低 | | **数据类型处理** | ✔️ 自动转换 | ❌ 手动拼接 | | **适用场景** | 动态参数查询、批量操作 | 静态 SQL(如 DDL 语句) | --- **实际开发建议**:优先使用 PreparedStatement,安全性更高且性能更优。仅在对 SQL 动态性要求极高(如动态表名)时,才考虑 Statement
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值