← MySQL 插入数据 MySQL 删除数据 →

Node.js使用mysql2执行MySQL UPDATE更新数据教程:单条、多列、批量与参数化查询

著
原创 2026-10-13 Node.js 已有人查阅

为什么数据更新是业务系统里最容易被低估的一环

做后台系统的人大多有一个共识:查询写得好不好,影响的是页面打开速度;更新写得好不好,影响的是数据质量和事故概率。MySQL的UPDATE语句本身并不复杂,真正容易出问题的地方在于——条件有没有写对、占位符有没有用、影响行数有没有判断、错误有没有兜住。Node.js通过mysql2操作MySQL时,这些细节会直接暴露在回调结果里,如果只是把SQL拼出来执行,短期内看不出毛病,时间一长就会积累成隐患。

这篇文章不打算把UPDATE讲成语法手册,而是按照实际项目里的使用顺序,把单条更新、多列更新、批量更新、用户输入传参、影响行数判断和错误处理串起来。每个环节都会给出可以直接运行的代码号学习编程示例,也会说明为什么这样写、什么情况下不推荐另一种写法。

UPDATE语句的基本结构

MySQL中修改已有记录使用UPDATE,基本形式如下:

UPDATE table_name
SET column1 = value1, column2 = value2
WHERE condition;

几个位置的含义需要明确:

  • table_name是目标表名;

  • SET后面列出要修改的列和新值,多列之间用逗号分隔;

  • WHERE决定哪些行会被更新。

WHERE子句是整条语句里最不能省的部分。省略之后,表中所有行都会被更新,这种操作在测试环境可能只是数据被改乱,在生产环境往往就是一次数据事故。实际写代码时,建议把WHERE条件当成UPDATE的必填项来对待。

在Node.js中建立连接并执行单条更新

先看一个最小可运行的例子。假设companydb数据库中有一张employees表,要把id为1的员工薪资调整为70000:

const mysql = require('mysql2');

const connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: 'your_password',
  database: 'companydb'
});

const sql = 'UPDATE employees SET salary = ? WHERE id = ?';

connection.query(sql, [70000, 1], (err, result) => {
  if (err) throw err;

  console.log('Record updated successfully');
  console.log('Affected Rows:', result.affectedRows);
});

connection.end();

这里直接把值写进SQL字符串也能跑通,但更推荐用?占位符。原因不复杂:占位符由mysql2负责转义,用户输入里即使包含引号或分号,也不会被当成SQL语法的一部分。这是防止SQL注入最直接的手段,也是实际项目里应该默认采用的写法。

更新之后如何确认数据已经生效

UPDATE执行成功只代表语句没有报错,不代表目标行的值一定符合预期。比较稳妥的做法是紧接着做一次SELECT校验:

connection.query(
  'SELECT * FROM employees WHERE id = ?',
  [1],
  (err, results) => {
    if (err) throw err;

    console.log(results);
  }
);

返回结果大致如下:

[
  {
    id: 1,
    name: 'John Smith',
    email: 'john.smith@example.com',
    department: 'IT',
    salary: 70000
  }
]

在调试阶段,这种「更新后立即查询」的习惯能省下不少排查时间。尤其是涉及多列更新时,眼睛看到的返回值比凭记忆判断更可靠。

一次更新多个列

业务里经常遇到需要同时修改多个字段的情况,比如员工转岗,部门和薪资一起调整:

const sql =
  "UPDATE employees SET department = ?, salary = ? WHERE id = ?";

connection.query(sql, ['Engineering', 75000, 1], (err, result) => {
  if (err) throw err;

  console.log('Record updated successfully');
});

多列更新写在一条语句里,比拆成多条UPDATE更合适。一方面减少数据库往返次数,另一方面避免出现「部门改了、薪资没改」这种中间状态。只要这些字段属于同一次业务动作,就应该放在同一个SET里。

批量更新符合条件的多行

UPDATE并不局限于单行。比如把所有IT部门的员工统一转到Engineering:

const sql =
  "UPDATE employees SET department = ? WHERE department = ?";

connection.query(sql, ['Engineering', 'IT'], (err, result) => {
  if (err) throw err;

  console.log('Records updated successfully');
  console.log('Affected Rows:', result.affectedRows);
});

输出中的AffectedRows会告诉你有多少行被这次操作影响。批量更新前,建议先用同样的WHERE条件执行一次SELECT,确认命中的范围符合预期,再执行UPDATE。这个习惯在涉及部门、状态、权限这类字段时尤其重要。

用参数化查询接收用户输入

只要更新值来自外部输入,就应该走占位符。下面这段代码把员工id和新薪资作为参数传入:

const employeeId = 2;
const newSalary = 60000;

const sql = 'UPDATE employees SET salary = ? WHERE id = ?';

connection.query(sql, [newSalary, employeeId], (err, result) => {
  if (err) throw err;

  console.log('Record updated successfully');
});

占位符数组的顺序和SQL中?出现的顺序一一对应。写的时候容易犯的一个错误是参数顺序放反,导致薪资被当成id、id被当成薪资。建议在SQL里保持「先SET值、后WHERE值」的书写顺序,参数数组也按同样顺序排列,减少出错概率。

affectedRows与changedRows的区别

mysql2返回的result对象里有两个容易混淆的属性:

  • affectedRows:匹配WHERE条件并作的行数;

  • changedRows:实际发生数据变化的行数。

如果某一行命中了WHERE,但新值和旧值相同,MySQL不会把它算作changed。也就是说,affectedRows可能是1,而changedRows是0。判断更新是否真正改变了数据时,changedRows更准确;判断是否有目标行被命中时,affectedRows更合适。

const sql = 'UPDATE employees SET salary = ? WHERE id = ?';

connection.query(sql, [80000, 3], (err, result) => {
  if (err) throw err;

  console.log('Affected Rows:', result.affectedRows);
  console.log('Changed Rows:', result.changedRows);
});

错误处理不能只靠throw

示例里常见if(err)throwerr;,这在演示代码里没问题,放到真实服务中并不合适。抛出的异常如果没有被上层捕获,可能导致进程退出。更稳妥的写法是记录错误并给出明确返回:

const sql = 'UPDATE employee SET salary = ? WHERE id = ?';

connection.query(sql, [70000, 1], (err, result) => {
  if (err) {
    console.error('Error:', err.message);
    return;
  }

  console.log('Record updated successfully');
});

上面这条语句故意把表名写成了employee,而实际表名是employees,执行后会得到类似提示:

Error: Table 'companydb.employee' doesn't exist

表名拼写、列名拼写、数据库连接中断,是UPDATE出错的主要来源。错误信息里通常会直接指出问题位置,养成先读err.message的习惯,比盲目改代码有效。

一个完整的更新流程示例

把连接、参数化更新、影响行数输出和连接关闭放在一起:

const mysql = require('mysql2');

const connection = mysql.createConnection({
  host: 'localhost',
  user: 'root',
  password: 'your_password',
  database: 'companydb'
});

const sql = 'UPDATE employees SET salary = ? WHERE id = ?';

connection.query(sql, [72000, 4], (err, result) => {
  if (err) {
    console.error(err);
    return;
  }

  console.log('Record updated successfully');
  console.log('Affected Rows:', result.affectedRows);
});

connection.end();

这段代码把id为4的员工薪资更新为72000,并输出受影响行数。实际项目中,连接通常来自连接池而不是每次createConnection,但更新语句本身的写法是一致的。

项目中的几点反思

第一,WHERE条件尽量走主键或唯一索引。用非索引列做条件更新,在数据量上来之后会拖慢执行速度,还可能因为锁范围过大影响其他请求。

第二,批量更新控制单次影响行数。一次更新几十万行,容易造成锁等待和主从延迟。可以按主键区间分批处理,每批几百到几千行,观察数据库负载再调整。

第三,更新前先备份或先查询。涉及金额、状态、权限的字段,执行前用SELECT确认命中范围,必要时把原值记录下来,出问题时有回退依据。

第四,不要在循环里逐条执行UPDATE。能用一条语句批量完成的,就不要拆成N条。确实需要逐条处理时,考虑用事务包起来,保证一致性。

第五,参数化查询不是可选项。只要值来自用户输入、接口参数或外部系统,就必须走占位符。字符串拼接在演示里省事,在真实系统里是风险点。

现在框架中的惯用写法

在Express、Koa、NestJS这类框架中,mysql2通常配合连接池使用:

const mysql = require('mysql2/promise');

const pool = mysql.createPool({
  host: 'localhost',
  user: 'root',
  password: 'your_password',
  database: 'companydb',
  waitForConnections: true,
  connectionLimit: 10
});

async function updateSalary(employeeId, newSalary) {
  const sql = 'UPDATE employees SET salary = ? WHERE id = ?';
  const [result] = await pool.execute(sql, [newSalary, employeeId]);

  return result.affectedRows;
}

使用promise版本配合async/await,可以让更新逻辑更清晰,也方便在上层统一做错误捕获。pool.execute与pool.query在参数化查询上的行为基本一致,execute会走预处理语句,在频繁执行的场景下更合适。

练习与思考

  1. 把本文单条更新的示例改成使用连接池和async/await的写法。

  2. 构造一条UPDATE,使affectedRows为1但changedRows为0,观察返回结果。

  3. 写一个批量更新函数,按主键区间分批更新,每批500行,并输出每批的affectedRows。

  4. 故意把表名写错,观察err.message的内容,思考在接口层如何把这类错误转换成对用户友好的提示。

← MySQL 插入数据 MySQL 删除数据 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号