返回

MySQL2中execute与query的区别及最佳实践

mysql

mysql2 中 execute 和 query 的区别与使用场景

遇到 NodeJS mysql2 库的 connection.execute()connection.query() 方法时, 你可能会疑惑它们的区别。简单来说, 就是预处理语句和参数的处理方式不同。

问题的根源:预处理语句

要理解这两者的差别, 先要弄明白什么是“预处理语句”。

MySQL 数据库支持预处理语句,这是一种优化数据库查询性能的方式。预处理语句,就是把 SQL 语句的模板先发送给数据库进行编译, 之后只需要发送参数, 就能执行这条 SQL, 不需要每次都重新编译 SQL 语句。

好处很明显:

  1. 性能提升 : 少了重复编译 SQL 语句的开销,特别是在循环执行同一条 SQL 语句时, 效果显著。
  2. 安全 : 能有效防止 SQL 注入。因为参数是单独发送的,数据库会正确处理参数,不会把参数当成 SQL 语句的一部分来执行。

executequery 的不同之处

mysql2 提供了两种执行 SQL 的方式:

  • connection.query(): 更像传统的方式,一次性发送完整的 SQL 语句和参数。
  • connection.execute(): 使用预处理语句。先发送 SQL 模板给 MySQL, MySQL 编译好等着。然后, 再把参数传过去执行。

用提供的例子来看看,这是三个不同的函数:

async function B1(connection) {
  const statement = await connection.prepare("SELECT 1 + ? + ?");
  const result1 = await statement.execute([1, 2]);
  const result2 = await statement.execute([3, 4]);
  await statement.close();
  return [result1, result2];
}

async function B2(connection) {
  const result1 = await connection.execute("SELECT 1 + ? + ?", [1, 2]);
  const result2 = await connection.execute("SELECT 1 + ? + ?", [3, 4]);
  return [result1, result2];
}

async function B3(connection) {
  const result1 = await connection.query("SELECT 1 + ? + ?", [1, 2]);
  const result2 = await connection.query("SELECT 1 + ? + ?", [3, 4]);
  return [result1, result2];
}

B1:手动预处理

B1 函数, 先用 connection.prepare() 手动创建一个预处理语句。之后, 多次调用 statement.execute() 并传入不同的参数,来复用这个预处理语句。 这就是标准的预处理语句用法,性能好,还安全。

B2: connection.execute() 的隐式复用?

问题来了,B2 中, 两次调用 connection.execute() , 每次都用同一个 SQL 模板 "SELECT 1 + ? + ?"。 mysql2 会不会自动帮我们复用预处理语句,省去重复编译的开销?

答案是: 会!

mysql2 内部会维护一个预处理语句缓存。当 connection.execute() 被调用时, 它会:

  1. 检查缓存:看看有没有和当前 SQL 模板匹配的已经准备好的语句。
  2. 如果有, 直接用缓存的语句,发送参数,执行。
  3. 如果没有, 就发送 SQL 模板去编译, 存到缓存里,然后再发送参数执行。

所以, B2 其实比 B3 更高效. B2 在第二次执行时可以利用缓存, 只需要发送参数. 而 B3 每次都要发送完整的 SQL 和参数, 没有预处理的优化。

B3: connection.query()

B3 用了 connection.query(), 没有预处理, 每次都发送完整的 SQL 语句和参数给 MySQL,开销比较大。

性能对比与选择

所以, 性能对比大致是: B1 ≈ B2 > B3。

  • 如果需要手动控制预处理语句的生命周期 (比如需要用到 statement.close()),用 B1 的方式。
  • 如果只需要简单执行,用 connection.execute() (像B2),mysql2 会自动处理缓存。
  • 尽量避免 connection.query() 搭配参数化查询的方式 (像 B3), 因为它享受不到预处理语句的好处。如果只是单次执行, 不需要参数的 SQL, query() 可以用。

进阶技巧与注意事项

缓存大小限制

mysql2 的预处理语句缓存有大小限制, 达到限制后, 会使用 LRU (Least Recently Used) 算法来淘汰旧的语句。可以用connection.config.namedPlaceholdersconnection.config.namedPlaceholders查看当前的缓存大小, 可以用如下方式在链接的时候进行设置:

const pool = mysql.createPool({
    host: 'localhost',
    user: 'root',
    database: 'test',
    waitForConnections: true,
    connectionLimit: 10,
    queueLimit: 0,
    namedPlaceholders: true,  //启用命名占位符,与缓存大小限制息息相关
    preparedStatementCacheSize: 250,  //自定义缓存的大小.默认是25
  });

preparedStatementCacheSize 参数控制最大数量, 超过就淘汰. 如果确定预处理语句类型不多,且调用频繁, 增大这个值会好一点。

命名参数

除了使用 ? 作为占位符, mysql2 还支持命名参数:

const result = await connection.execute(
  'SELECT * FROM users WHERE id = :id AND status = :status',
  { id: 123, status: 'active' }
);

这样, SQL 更容易阅读, 特别是参数比较多的时候。

什么时候用 query()?

虽说 execute() 通常更好, 但有些情况用 query() 也行:

  1. 一次性查询 : 如果只执行一次, 没必要预处理。
  2. 不带参数的查询 : 比如 SELECT VERSION() 这种,不需要预处理。
  3. 执行非 SELECT/INSERT/UPDATE/DELETE 语句 : 有些命令, 比如 SET , SHOW, 某些情况下不一定能用预处理语句。

安全建议

不管用 execute() 还是 query(), 都要用参数化查询, 避免直接拼接 SQL 字符串, 这是防止 SQL 注入的铁律!

// 千万不要这样做!
const userId = req.query.userId;
const sql = "SELECT * FROM users WHERE id = " + userId;
const result = await connection.query(sql); // 大错特错!

总结下, 在使用 mysql2 时,推荐尽量用 connection.execute(), 让库自动管理预处理语句, 既安全又高效。记住, 用参数化查询, 不要直接拼接字符串!