← MySQL 删除数据 MYSQL 去重查询 →

Node.js mysql2执行MySQL SELECT查询:全表、指定列、WHERE、ORDERBY、LIMIT、COUNT与参数化实践

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

读取是数据库操作里最频繁的动作

一个业务系统里,写入操作占比通常不高,大量请求都发生在读上。列表页、详情页、搜索结果、统计报表,背后都是一条条SELECT语句。SELECT写得好不好,直接影响接口响应速度和数据库负载。

本课程围绕Node.js+mysql2执行MySQLSELECT查询展开,把常见的查询形态、参数化写法和结果处理讲清楚。

SELECT在做什么

SELECT用于从一张或多张表中检索数据。它可以返回全部列,也可以只取需要的列;可以返回所有行,也可以用WHERE过滤出满足条件的行。Node.js把SELECT发给MySQL,MySQL返回匹配的行,mysql2把这些行整理成一个JavaScript数组,数组里每个元素是一个对象,对象的键就是列名。

环境准备

动手前确认:

  • 系统已安装Node.js

  • MySQLServer已安装并处于运行状态

  • 项目里已安装mysql2包

  • 目标数据库和Employees表已存在并有数据

安装mysql2:

npm install mysql2

示例统一使用下面这张Employees表:

employeeId employeeName department salary
1 JohnSmith HR 45000
2 EmmaDavis IT 65000
3 MichaelBrown Finance 55000
4 SophiaWilson IT 70000
5 JamesTaylor Sales 50000

后续示例默认已经创建好MySQL连接,不再重复连接代码。

SELECT的基本语法

SELECT column_name(s) FROM table_name;

column_name(s)指定要读取的列,用*表示全部列;table_name指定从哪张表读。

查询全部记录

const sql = "SELECT * FROM Employees";

connection.query(sql, (err, results) => {
  if (err) {
    console.log(err.message);
    return;
  }
  console.log(results);
});

返回结果是一个数组:

[
  { employeeId: 1, employeeName: 'John Smith', department: 'HR', salary: 45000 },
  { employeeId: 2, employeeName: 'Emma Davis', department: 'IT', salary: 65000 },
  { employeeId: 3, employeeName: 'Michael Brown', department: 'Finance', salary: 55000 },
  { employeeId: 4, employeeName: 'Sophia Wilson', department: 'IT', salary: 70000 },
  { employeeId: 5, employeeName: 'James Taylor', department: 'Sales', salary: 50000 }
]

SELECT*写起来省事,但项目里不太建议在业务查询里长期使用。原因有两个:一是表结构变化时,返回字段会跟着变,前端如果依赖字段顺序容易出问题;二是宽表里很多列用不上,全查出来会增加网络传输和内存开销。列表接口按需取列更稳妥。

只查需要的列

const sql = "SELECT employeeName, department, salary FROM Employees";

connection.query(sql, (err, results) => {
  if (err) throw err;
  console.log(results);
});

返回的对象里就只有这三列。字段少了,序列化成本也低,接口响应体积更小。

用WHERE过滤记录

const sql = "SELECT * FROM Employees WHERE department = 'IT'";

connection.query(sql, (err, results) => {
  if (err) throw err;
  console.log(results);
});

返回IT部门的员工:

[
  { employeeId: 2, employeeName: 'Emma Davis', department: 'IT', salary: 65000 },
  { employeeId: 4, employeeName: 'Sophia Wilson', department: 'IT', salary: 70000 }
]

WHERE后面跟的是过滤条件,只有条件为真的行才会出现在结果里。

用参数化查询处理外部传入的值

把变量直接拼进SQL字符串是常见的安全隐患。假设部门名来自前端:

const sql = `SELECT * FROM Employees WHERE department = '${department}'`;

如果department被构造成IT'OR'1'='1,条件就恒真,整张表的数据都会被读出来。正确做法是用?占位符:

const department = "IT";
const sql = "SELECT * FROM Employees WHERE department = ?";

connection.query(sql, [department], (err, results) => {
  if (err) throw err;
  console.log(results);
});

mysql2会把占位符替换成经过转义的值,字符串自动加引号,特殊字符被处理,注入串失去作用。这是mysql2在参数化查询上最实际的价值。

用ORDERBY排序

升序:

const sql = "SELECT * FROM Employees ORDER BY salary ASC";

connection.query(sql, (err, results) => {
  if (err) throw err;
  console.log(results);
});

结果按薪资从低到高:

John Smith - 45000
James Taylor - 50000
Michael Brown - 55000
Emma Davis - 65000
Sophia Wilson - 70000

降序:

const sql = "SELECT * FROM Employees ORDER BY salary DESC";

结果按薪资从高到低:

Sophia Wilson - 70000
Emma Davis - 65000
Michael Brown - 55000
James Taylor - 50000
John Smith - 45000

ORDERBY后面可以跟多个字段,比如先按department升序,同一部门内再按salary降序:

SELECT * FROM Employees ORDER BY department ASC, salary DESC

需要注意,排序字段如果没有索引,数据量大时MySQL会走filesort,性能开销明显。列表接口常用的排序字段建议加索引。

用LIMIT限制返回行数

const sql = "SELECT * FROM Employees LIMIT 3";

connection.query(sql, (err, results) => {
  if (err) throw err;
  console.log(results);
});

只返回前三行。LIMIT最常见的用途是分页,配合OFFSET使用:

SELECT * FROM Employees LIMIT 10 OFFSET 20

表示跳过前20行,取接下来的10行。深分页时OFFSET越大,MySQL扫描的行越多,性能越差。数据量大的列表建议用游标分页,比如WHEREemployeeId>上一页之后一个idLIMIT10。

用COUNT统计记录数

const sql = "SELECT COUNT(*) AS totalEmployees FROM Employees";

connection.query(sql, (err, results) => {
  if (err) throw err;
  console.log(results);
});

返回:

[ { totalEmployees: 5 } ]

AS给统计列起了别名,结果对象的键就是别名。分页场景里经常要同时拿列表和总数,一般拆成两条查询:一条COUNT拿总数,一条带LIMIT拿当前页数据。COUNT本身在InnoDB里需要扫描索引,大表上频繁调用要留意。

用AND组合多个条件

const sql = `
  SELECT *
  FROM Employees
  WHERE department = 'IT'
  AND salary > 60000
`;

connection.query(sql, (err, results) => {
  if (err) throw err;
  console.log(results);
});

返回IT部门且薪资大于60000的员工:

[
  { employeeId: 2, employeeName: 'Emma Davis', department: 'IT', salary: 65000 },
  { employeeId: 4, employeeName: 'Sophia Wilson', department: 'IT', salary: 70000 }
]

AND要求两个条件同时成立。

用OR组合多个条件

const sql = `
  SELECT *
  FROM Employees
  WHERE department = 'IT'
  OR department = 'HR'
`;

connection.query(sql, (err, results) => {
  if (err) throw err;
  console.log(results);
});

返回IT或HR部门的员工:

[
  { employeeId: 1, employeeName: 'John Smith', department: 'HR', salary: 45000 },
  { employeeId: 2, employeeName: 'Emma Davis', department: 'IT', salary: 65000 },
  { employeeId: 4, employeeName: 'Sophia Wilson', department: 'IT', salary: 70000 }
]

OR只要有一个条件成立就返回。AND和OR混用时要注意优先级,AND的优先级高于OR,不确定时用括号明确分组:

WHERE (department = 'IT' OR department = 'HR') AND salary > 50000

上面这条如果不加括号,含义就变成“IT部门,或者HR且薪资大于50000”,跟预期不同。

处理空结果

查询不一定有匹配的行,比如查一个不存在的部门:

const sql = "SELECT * FROM Employees WHERE department = 'Marketing'";

connection.query(sql, (err, results) => {
  if (err) throw err;

  if (results.length === 0) {
    console.log("没有找到匹配的记录");
    return;
  }

  console.log(results);
});

SELECT查不到数据不会报错,返回的是空数组[]。业务代码里要显式判断results.length,否则前端拿到空列表可能显示异常。详情页场景更要区分“没查到”和“查询失败”这两种情况。

查询错误的处理

表名写错、字段不存在、语法有误,都会通过err返回:

const sql = "SELECT * FROM EmployeeList";

connection.query(sql, (err, results) => {
  if (err) {
    console.log("错误:", err.message);
    return;
  }
  console.log(results);
});

输出:

错误: Table 'companydb.EmployeeList' doesn't exist

实际表名是Employees,这里写成EmployeeList,MySQL直接返回表不存在的错误。回调风格里必须判断err并return,否则后面的代码会拿着undefined继续跑,报出更难定位的错误。

完整示例:条件查询+排序+分页

const mysql = require('mysql2');

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

const department = 'IT';
const minSalary = 60000;
const pageSize = 10;
const offset = 0;

const sql = `
  SELECT employeeId, employeeName, department, salary
  FROM Employees
  WHERE department = ?
  AND salary >= ?
  ORDER BY salary DESC
  LIMIT ? OFFSET ?
`;

connection.query(
  sql,
  [department, minSalary, pageSize, offset],
  (err, results) => {
    if (err) {
      console.error("查询失败:", err.message);
      return;
    }

    if (results.length === 0) {
      console.log("没有符合条件的员工");
      return;
    }

    console.log("查询结果:", results);
  }
);

connection.end();

这段代码把参数化查询、条件过滤、排序、分页组合在一起,参数按SQL里?出现的顺序依次传入数组。LIMIT和OFFSET也用占位符传,避免手拼字符串。

现在写法:Promise与async/await

mysql2提供promise版本,配合async/await写起来更线性,错误处理可以用try/catch统一收口:

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

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

  try {
    const sql = "SELECT employeeId, employeeName, salary FROM Employees WHERE department = ?";
    const [rows] = await connection.execute(sql, [department]);
    return rows;
  } catch (err) {
    console.error("查询失败:", err.message);
    throw err;
  } finally {
    await connection.end();
  }
}

connection.execute()走预处理语句路径,和query一样支持?占位符。解构赋值const[rows]拿到的是结果行数组,第二个元素是字段元信息,一般用不到。finally里关闭连接,保证异常时连接也能释放。

本节课程知识要点

  • SELECT返回的结果是数组,每个元素是一行记录,键为列名

  • 业务查询尽量避免SELECT*,按需取列减少传输和序列化开销

  • 外部传入的值一律走?占位符,不要拼接字符串

  • AND优先级高于OR,混用时用括号明确分组

  • 查不到数据返回空数组,不报错,业务代码要显式判断length

  • 排序字段和常用过滤字段建议加索引,避免filesort和全表扫描

  • 深分页用游标方式替代大OFFSET

  • 回调里必须判断err并return,promise版本用try/catch/finally

练习思考题

  1. 写一条SELECT语句,查询salary在50000到70000之间、department为IT或Finance的员工,按salary降序排列。

  2. 把上面的回调写法改写成mysql2/promise+async/await版本,要求查询出错时抛出异常,空结果时返回空数组。

  3. 分页接口需要同时返回当前页数据和总记录数,写出这两条SQL,并说明为什么不在一条SQL里完成。

  4. WHEREdepartment='IT'ORdepartment='HR'ANDsalary>50000这条语句的实际过滤逻辑是什么?如何改写成“IT或HR部门且薪资大于50000”?

← MySQL 删除数据 MYSQL 去重查询 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号