DCL(Data Control Language,数据控制语言)用于管理数据库账号和权限,日常核心命令包括 CREATE USER、GRANT、REVOKE 和 SHOW GRANTS。
本文以 MySQL 8.0 为准。账号管理通常需要管理员权限,生产环境应遵循最小权限原则。
MySQL 账号由“用户名 + 来源主机”共同确定。
'app_user'@'localhost'与'app_user'@'10.%'是两个不同账号。
SELECT USER(), CURRENT_USER();
USER():客户端连接时提供的用户名和来源。CURRENT_USER():MySQL 实际用于权限检查的账号。SHOW GRANTS;
SHOW GRANTS FOR 'app_user'@'localhost';
SELECT User, Host, plugin, account_locked
FROM mysql.user
ORDER BY User, Host;
查询 mysql.user 需要相应管理权限。不要把系统表的查询权限授予普通业务账号。
CREATE USER 'shop_app'@'localhost'
IDENTIFIED BY '请替换为高强度随机密码';
CREATE USER 'shop_app'@'10.10.%'
IDENTIFIED BY '请替换为高强度随机密码';
不要为图方便默认使用任意来源主机:
-- 风险较高,除非网络边界和业务确实需要
CREATE USER 'shop_app'@'%'
IDENTIFIED BY '请替换为高强度随机密码';
'%' 表示允许从任意主机来源匹配。即使数据库端口有防火墙保护,也应尽量限制 Host 范围。
CREATE USER 'remote_app'@'10.10.%'
IDENTIFIED BY '请替换为高强度随机密码'
REQUIRE SSL;
启用 REQUIRE SSL 后,客户端必须正确配置 TLS。它只保证连接要求,不替代证书验证、网络访问控制和密码管理。
MySQL 可以处理 Unicode 数据,但数据库账号名、角色名、数据库名和表名建议使用简洁的 ASCII 英文命名:
shop_app
shop_readonly
shop_migration
原因:
业务表中的中文内容仍应使用 utf8mb4,账号命名与业务数据字符集是两个不同问题。
GRANT SELECT, INSERT, UPDATE, DELETE
ON shop.*
TO 'shop_app'@'localhost';
业务应用通常不需要 CREATE、ALTER、DROP、GRANT OPTION 等高风险权限。
CREATE USER 'shop_readonly'@'10.10.%'
IDENTIFIED BY '请替换为高强度随机密码'
REQUIRE SSL;
GRANT SELECT
ON shop.*
TO 'shop_readonly'@'10.10.%';
适用于报表、排障查询和只读后台。只读账号也可能执行高成本查询,因此仍需要连接数、超时和资源层面的控制。
GRANT SELECT, UPDATE
ON shop.products
TO 'inventory_service'@'10.10.%';
GRANT SELECT (id, username, status)
ON shop.users
TO 'support_reader'@'10.10.%';
列级权限可以减少敏感字段暴露,但权限管理会更复杂。真实系统还可以通过视图提供经过筛选的数据。
CREATE USER 'shop_migration'@'localhost'
IDENTIFIED BY '请替换为高强度随机密码';
GRANT SELECT, INSERT, UPDATE, DELETE,
CREATE, ALTER, DROP, INDEX, REFERENCES
ON shop.*
TO 'shop_migration'@'localhost';
迁移账号权限高于普通应用账号,应限制来源、妥善保管密码,并只在发布或迁移期间使用。
GRANT EXECUTE
ON PROCEDURE shop.recalculate_order_amount
TO 'shop_app'@'localhost';
MySQL 8.0 支持角色。角色适合多个账号共享同一组权限,减少重复授权。
CREATE ROLE 'shop_app_role', 'shop_readonly_role';
GRANT SELECT, INSERT, UPDATE, DELETE
ON shop.*
TO 'shop_app_role';
GRANT SELECT
ON shop.*
TO 'shop_readonly_role';
GRANT 'shop_app_role'
TO 'shop_app'@'localhost';
SET DEFAULT ROLE 'shop_app_role'
TO 'shop_app'@'localhost';
如果没有设置默认角色,用户登录后角色可能不会自动生效。可以查看:
SELECT CURRENT_ROLE();
当前会话启用所有已授予角色:
SET ROLE ALL;
REVOKE DELETE
ON shop.*
FROM 'shop_app'@'localhost';
REVOKE ALL PRIVILEGES
ON shop.*
FROM 'shop_app'@'localhost';
REVOKE 'shop_app_role'
FROM 'shop_app'@'localhost';
撤销后使用 SHOW GRANTS 再次确认:
SHOW GRANTS FOR 'shop_app'@'localhost';
ALTER USER 'shop_app'@'localhost'
IDENTIFIED BY '新的高强度随机密码';
修改密码后,应用连接池中的旧连接可能仍暂时存在,新建连接会使用新密码。生产环境应制定密码轮换和应用配置更新顺序。
ALTER USER 'shop_app'@'localhost' ACCOUNT LOCK;
ALTER USER 'shop_app'@'localhost' ACCOUNT UNLOCK;
账号异常、离职交接或暂时停用时,锁定通常比立即删除更容易恢复。
RENAME USER
'shop_app'@'localhost'
TO
'shop_app'@'10.10.%';
修改 Host 会改变允许匹配的连接来源,执行前应确认应用服务器地址和网络策略。
DROP USER IF EXISTS 'shop_app'@'localhost';
DROP ROLE IF EXISTS 'shop_app_role';
删除前检查:
SHOW GRANTS FOR 'shop_app'@'localhost';
还应确认配置中心、CI/CD、定时任务和其他服务是否仍在使用该账号。
-- 全局范围
ON *.*
-- 数据库范围
ON shop.*
-- 表范围
ON shop.orders
授权范围越大,账号被泄露后的影响越大。优先限制到真实需要的数据库或表。
| 权限 | 作用 | 常见账号 |
|---|---|---|
SELECT |
查询数据 | 应用、只读账号 |
INSERT |
新增数据 | 应用账号 |
UPDATE |
更新数据 | 应用账号 |
DELETE |
删除数据 | 应用账号,按需授予 |
CREATE |
创建对象 | 迁移账号 |
ALTER |
修改表结构 | 迁移账号 |
DROP |
删除对象 | 迁移账号,谨慎授予 |
INDEX |
创建或删除索引 | 迁移账号 |
REFERENCES |
创建外键 | 迁移账号 |
EXECUTE |
执行存储过程或函数 | 按需授予 |
GRANT OPTION |
把自己的权限继续授予他人 | 仅权限管理员 |
不要给普通业务账号授予:
GRANT ALL PRIVILEGES ON *.* ... WITH GRANT OPTION;
这会显著扩大账号泄露或 SQL 注入后的影响范围。
FLUSH PRIVILEGES 是否需要通过标准账号和权限语句操作时:
CREATE USER ...;
ALTER USER ...;
GRANT ...;
REVOKE ...;
DROP USER ...;
通常不需要执行 FLUSH PRIVILEGES,变更会立即生效。
不应直接修改 mysql.user 等系统权限表。只有在特殊维护场景中直接修改了授权表,才可能需要重新加载权限;正常项目不应采用这种方式。
数据库连接配置示意:
import mysql, { type PoolOptions } from "mysql2/promise";
const password: string | undefined = process.env.DB_PASSWORD;
if (password === undefined) {
throw new Error("缺少 DB_PASSWORD 环境变量");
}
const poolOptions: PoolOptions = {
host: process.env.DB_HOST ?? "127.0.0.1",
port: 3306,
user: "shop_app",
password,
database: "shop",
connectionLimit: 10,
charset: "utf8mb4",
};
export const pool = mysql.createPool(poolOptions);
实践建议:
utf8mb4。mysql2 的具体字符集参数要以当前驱动版本文档为准。连接建立后可以查询 @@character_set_client 等变量,验证配置是否真正生效。
检查实际匹配账号:
SELECT USER(), CURRENT_USER();
SHOW GRANTS;
常见原因是 Host 不匹配,例如授权给 'shop_app'@'localhost',但应用从另一台服务器连接。
这通常不是 DCL 权限问题,应检查:
SELECT
@@character_set_client,
@@character_set_connection,
@@character_set_results;
同时检查数据库、表、字段字符集和 Node.js 连接配置是否统一为 utf8mb4。
SELECT 不会直接修改业务数据,但没有索引的大查询、全表扫描和复杂聚合仍可能占用 CPU、内存和 I/O。权限控制不能替代查询治理和资源限制。
通常不需要。把运行应用和执行数据库迁移分开:
shop_app:日常 SELECT、INSERT、UPDATE、DELETE。shop_migration:发布期间执行 CREATE、ALTER、DROP、INDEX。这样即使应用发生 SQL 注入,攻击范围也不会直接扩大到删除表结构。
ALL PRIVILEGES ON *.*?GRANT OPTION?SHOW GRANTS 和测试账号验证?utf8mb4?-- 创建应用账号
CREATE USER 'shop_app'@'localhost'
IDENTIFIED BY '高强度随机密码';
-- 授予 CRUD 权限
GRANT SELECT, INSERT, UPDATE, DELETE
ON shop.*
TO 'shop_app'@'localhost';
-- 创建只读账号
CREATE USER 'shop_readonly'@'10.10.%'
IDENTIFIED BY '高强度随机密码'
REQUIRE SSL;
GRANT SELECT
ON shop.*
TO 'shop_readonly'@'10.10.%';
-- 查看权限
SHOW GRANTS FOR 'shop_app'@'localhost';
-- 撤销权限
REVOKE DELETE
ON shop.*
FROM 'shop_app'@'localhost';
-- 修改密码
ALTER USER 'shop_app'@'localhost'
IDENTIFIED BY '新的高强度随机密码';
-- 锁定和解锁
ALTER USER 'shop_app'@'localhost' ACCOUNT LOCK;
ALTER USER 'shop_app'@'localhost' ACCOUNT UNLOCK;
-- 删除账号
DROP USER IF EXISTS 'shop_app'@'localhost';