首页 / Node.js 教程 / MySQL 高级查询

Node.js 教程

MySQL 高级查询

本教程共 76 篇 · 第 50 篇 · 更新于 2026-07-25 · 约 4 分钟阅读

Node.jsMySQL事务联表索引

50. MySQL 高级查询

本节目标:联表查询、事务、索引和查询优化。

单表查询不够用了。真实业务里,数据分散在多张表中,需要 JOIN 关联;结果集太大,需要分页;还有排序、聚合统计等需求。本章把这些高级查询逐个拆解。

WHERE 条件

条件查询不只是等号:

// 范围
await pool.execute('SELECT * FROM products WHERE price BETWEEN ? AND ?', [10, 100]);

// IN 列表
await pool.execute('SELECT * FROM users WHERE status IN (?, ?)', ['active', 'vip']);

// LIKE 模糊查询(% 通配符)
await pool.execute('SELECT * FROM users WHERE name LIKE ?', ['%Alice%']);

// 多条件组合
await pool.execute(
  'SELECT * FROM orders WHERE status = ? AND created_at > ?',
  ['paid', '2024-01-01']
);
Tip

LIKE '%xxx%' 无法走索引,数据量大时会很慢。需要全文检索考虑 MySQL 的 FULLTEXT 索引,或者上 Elasticsearch。

ORDER BY 排序

// 按价格升序
await pool.execute('SELECT * FROM products ORDER BY price ASC');

// 按创建时间倒序,最新的在前
await pool.execute('SELECT * FROM posts ORDER BY created_at DESC');

// 多字段排序:先按状态、再按时间
await pool.execute('SELECT * FROM tasks ORDER BY status ASC, deadline ASC');

LIMIT 分页

MySQL 用 LIMIT offset, count 做分页:

async function getPage(page = 1, size = 10) {
  const offset = (page - 1) * size;
  const [rows] = await pool.execute(
    'SELECT * FROM users ORDER BY id DESC LIMIT ?, ?',
    [offset, size]
  );
  return rows;
}

分页时通常还要查总条数:

const [[{ total }]] = await pool.execute('SELECT COUNT(*) AS total FROM users');

封装一个通用的分页响应:

async function paginate(table, page = 1, size = 10, orderBy = 'id DESC') {
  const offset = (page - 1) * size;
  const [rows] = await pool.execute(
    `SELECT * FROM ${table} ORDER BY ${orderBy} LIMIT ?, ?`,
    [offset, size]
  );
  const [[{ total }]] = await pool.execute(`SELECT COUNT(*) AS total FROM ${table}`);
  return { data: rows, pagination: { page, size, total } };
}
Warning

上面的 orderBy 直接拼进 SQL 有注入风险。生产环境要加白名单校验,只允许特定字段和方向。

JOIN 多表关联

假设有两张表:users(用户)和 orders(订单),通过 user_id 关联。

INNER JOIN:只返回有匹配的记录

const [rows] = await pool.execute(`
  SELECT orders.id, orders.amount, users.name
  FROM orders
  INNER JOIN users ON orders.user_id = users.id
  WHERE orders.status = ?
`, ['paid']);

LEFT JOIN:返回左表全部,右表没匹配填 NULL

const [rows] = await pool.execute(`
  SELECT users.name, COUNT(orders.id) AS order_count
  FROM users
  LEFT JOIN orders ON users.id = orders.user_id
  GROUP BY users.id
`);

自关联:查用户的推荐人

SELECT u.name, r.name AS referrer_name
FROM users u
LEFT JOIN users r ON u.referrer_id = r.id

聚合函数

// 统计总金额
const [[{ total }]] = await pool.execute(
  'SELECT SUM(amount) AS total FROM orders WHERE status = ?',
  ['paid']
);

// 平均价格
const [[{ avg }]] = await pool.execute('SELECT AVG(price) AS avg FROM products');

// 分组统计每个用户的订单数
const [rows] = await pool.execute(`
  SELECT user_id, COUNT(*) AS cnt, SUM(amount) AS total
  FROM orders
  GROUP BY user_id
  HAVING cnt >= 2
`);

HAVINGWHERE 的区别:WHERE 过滤原始行,HAVING 过滤聚合后的组。

子查询

// 查下过单的用户
const [rows] = await pool.execute(`
  SELECT * FROM users
  WHERE id IN (SELECT DISTINCT user_id FROM orders)
`);

子查询可读性好,但性能往往不如 JOIN。MySQL 8 以后可以用 CTE(公用表表达式)写得更清晰:

WITH active_users AS (
  SELECT DISTINCT user_id FROM orders
)
SELECT * FROM users WHERE id IN (SELECT user_id FROM active_users);

索引提示

查询慢的时候,用 EXPLAIN 看看执行计划:

EXPLAIN SELECT * FROM users WHERE email = 'alice@example.com';

如果 type 列是 ALL,说明在扫全表。给 email 加索引:

CREATE INDEX idx_email ON users(email);

索引不是越多越好,写操作(INSERT/UPDATE/DELETE)每次都要维护索引,会拖慢写入速度。只在频繁查询、很少修改的字段上加。