索引
索引是数据库中用于加快数据检索速度的独立结构,类似于书的目录。下面从概念、原理到实战全面讲解。
| 类型 | 说明 | 示例 |
|---|---|---|
| 主键索引 | 特殊的唯一索引,非空,一张表只能有一个 | PRIMARY KEY (id) |
| 唯一索引 | 索引列的值必须唯一,允许 NULL | UNIQUE INDEX idx_email (email) |
| 普通索引 | 基本索引,无唯一性限制 | INDEX idx_name (name) |
| 全文索引 | 用于文本搜索,支持 MATCH ... AGAINST |
FULLTEXT INDEX ft_article (content) |
| 空间索引 | 用于地理空间数据类型(POINT 等) |
SPATIAL INDEX sp_loc (location) |
| 前缀索引 | 只索引列值的前缀部分,节省空间 | INDEX idx_title (title(20)) |
创建与删除索引
sql
-- 建表时创建
CREATE TABLE t (
id INT PRIMARY KEY, -- 主键索引
name VARCHAR(50),
email VARCHAR(100),
INDEX idx_name (name), -- 普通索引
UNIQUE INDEX uk_email (email)-- 唯一索引
);
-- 建表后创建
CREATE INDEX idx ON 表名(列名);
ALTER TABLE 表名 ADD INDEX idx (列名);
-- 创建复合索引
CREATE INDEX idx_multi ON orders (user_id, status);
-- 删除索引
DROP INDEX idx ON 表名;
ALTER TABLE 表名 DROP INDEX idx;
最左前缀原则
对于复合索引 (col1, col2, col3),查询条件必须包含 col1 才能利用索引。例如:
sql
-- 能利用索引
WHERE col1 = 1
WHERE col1 = 1 AND col2 = 2
WHERE col1 = 1 AND col2 = 2 AND col3 = 3
-- 不能利用索引(未从 col1 开始)
WHERE col2 = 2
WHERE col2 = 2 AND col3 = 3
覆盖索引
查询的所有列都在索引中,不需要回表,极大提升性能。
sql
-- 假设有索引 (user_id, status)
SELECT user_id, status FROM orders WHERE user_id = 1;
-- 不需要回表
查看索引使用情况
使用 EXPLAIN 分析查询:
sql
EXPLAIN SELECT * FROM student WHERE name = '张三';
关注以下字段:
- key:实际使用的索引名(NULL 表示未使用)。
- rows:扫描的行数。
- type :连接类型,从优到差依次是:
system/const:主键或唯一索引等值查询,最快。eq_ref:关联查询时使用主键或唯一索引。ref:非唯一索引等值查询。range:索引范围扫描(BETWEEN、>、<、IN)。index:全索引扫描(比全表扫描稍好)。ALL:全表扫描,最差,必须优化。
- Extra :
Using index表示覆盖索引,Using filesort表示需要额外排序(可优化)。
全文索引而非 LIKE '%...%'。
存储过程
存储过程将多条 SQL 语句封装成一个可重复调用的单元,存储在数据库服务器上。客户端只需通过 CALL 命令执行它,无需每次传输多条语句。
简单类比:就像编程语言中的函数,将经常使用的操作打包,输入参数就能得到结果。
创建存储过程
sql
DELIMITER //
CREATE PROCEDURE 过程名([参数列表])
BEGIN
-- SQL 语句
END //
DELIMITER ;
创建存储过程,删除一个指定学生记录。
sql
USE xscj;
DELIMITER $$
CREATE PROCEDURE delete_xs(IN xh char(6))
-- IN:输入参数,调用时必须提供一个值,过程中只读。
-- xh char(6):参数名为 xh,固定长度字符串,最多 6 个字符(对应学号字段类型)。
BEGIN
DELETE FROM xs WHERE学号=xh;
SELECT * FROM xs;
END $$
DELIMITER ;
存储过程的调用
sql
CALL delete_xs('221101');
删除存储过程
sql
DROP PROCEDURE [IF EXISTS] 过程名;
查看存储过程
sql
-- 查看所有存储过程状态
SHOW PROCEDURE STATUS;
-- 查看指定数据库的存储过程
SHOW PROCEDURE STATUS WHERE Db = '数据库名';
-- 查看创建语句
SHOW CREATE PROCEDURE 过程名;
存储过程体
sql
CREATE PROCEDURE 过程名(参数列表)
BEGIN
-- 这里就是存储过程体
-- 可以包含变量声明、条件判断、循环、游标、错误处理等
END
变量声明
sql
DECLARE 变量名 数据类型 [DEFAULT 默认值];
sql
DECLARE total INT DEFAULT 0;
DECLARE student_name VARCHAR(50);
变量赋值
sql
SET 变量名 = 值;
-- 或者
SELECT 列名 INTO 变量名 FROM 表 WHERE 条件;
通过变量显示xs表中指定学号学生姓名和专业名的值(分别赋给变量)。
sql
DELIMITER $$
CREATE PROCEDURE xscj.xs_vdisp
(IN xhchar(6) )
BEGIN
DECLARE name, project varchar(10);
SELECT 姓名,专业INTO name, project
FROM xs
WHERE 学号= xh;
SELECT name, project;
END$$
DELIMITER ;
CALL xscj.xs_vdisp('221101');
条件判断
if语句
sql
IF 条件 THEN
语句;
ELSEIF 条件 THEN
语句;
ELSE
语句;
END IF;
创建存储过程,比较第1个参数和第2个参数整数大小,显示比较结果。
sql
DELIMITER $$
CREATE PROCEDURE xscj.intComp
(IN k1 integer, IN k2 integer, OUT k3 char(6) )
BEGIN
IF k1>k2 THEN
SET k3= '大于';
ELSEIF k1=k2 THEN
SET k3= '等于';
ELSE
SET k3= '小于';
END IF;
END$$
DELIMITER ;
CALL xscj.intComp(12,46, @k3);
SELECT @k3;
case语句
sql
CASE 表达式
WHEN 值1 THEN 语句;
WHEN 值2 THEN 语句;
ELSE 语句;
END CASE;
创建一个存储过程,把百分制变成等级。
sql
DELIMITER $$
CREATE PROCEDURE xscj.cjGrade
(IN cjtinyint(1), OUT grade varchar(4) )
BEGIN
CASE
WHEN cj>=90 THEN SETgrade='优秀';
WHEN cj>=80 AND cj<90 THEN SET grade ='良好';
WHEN cj>=70 AND cj<80 THEN SET grade ='中等';
WHEN cj>=60 AND cj<70 THEN SET grade ='及格';
ELSE SET grade ='不及格';
SELECT grade;
END CASE;
END$$
DELIMITER ;
CALL xscj.cjGrade(85, @gcj);
SELECT @gcj;
while循环(先判断后执行)
sql
WHILE 条件 DO
语句;
END WHILE;
采用WHILE计算1+2+3+...+n。
sql
USE xscj;
DROP PROCEDURE IF EXISTS proc_nsum;
CREATE PROCEDURE proc_nsum()
BEGIN
DECLARE n, s int DEFAULT 0;
WHILE n <= @n DO
SET s = s + n;
SET n = n + 1;
END WHILE;
SELECT '1+2+3+...+',@n,'=',s;
END ;
SET @n = 50;
CALL proc_nsum();
REPEAT(先执行后判断,至少执行一次)
sql
REPEAT
语句;
UNTIL 条件 END REPEAT;
存储函数
创建
sql
CREATE FUNCTION 函数名(参数列表)
RETURNS 返回数据类型
[DETERMINISTIC | NOT DETERMINISTIC]
[CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA]
BEGIN
-- 函数体
RETURN 值;
END
调用
sql
-- 直接调用
SELECT 函数名(参数);
-- 在表达式中使用
SELECT 姓名, 函数名(参数) AS 自定义列 FROM 表;
WHERE 函数名(列) > 值;
SET @变量 = 函数名(参数);
查看与删除
sql
-- 查看所有函数状态
SHOW FUNCTION STATUS;
-- 筛选指定库
SHOW FUNCTION STATUS WHERE Db = 'xscj';
-- 查看创建定义
SHOW CREATE FUNCTION 函数名;
-- 删除
DROP FUNCTION IF EXISTS 函数名;
创建一个存储函数,把百分制变成等级。
sql
USE xscj;
DROP FUNCTION IF EXISTS xscj.cjGradeFun;
DELIMITER $$
CREATE FUNCTION xscj.cjGradeFun(cj tinyint(1))
RETURNS char(3)
BEGIN
DECLARE grade char(3);
CASE
WHEN cj>=90 THEN SET grade='优秀';
WHEN cj>=80 AND cj<90 THEN SET grade='良好';
WHEN cj>=70 AND cj<80 THEN SET grade='中等';
WHEN cj>=60 AND cj<70 THEN SET grade='及格';
ELSE SET grade='不及格';
END CASE;
RETURN grade;
END$$
DELIMITER ;
SET @cj=85;
SELECT @cj,xscj.cjGradeFun(@cj);
根据学号返回姓名
sql
DELIMITER //
CREATE FUNCTION get_student_name(sid CHAR(6))
RETURNS VARCHAR(50)
DETERMINISTIC READS SQL DATA
BEGIN
DECLARE s_name VARCHAR(50);
SELECT 姓名 INTO s_name FROM xs WHERE 学号 = sid;
RETURN IFNULL(s_name, '未知');
END //
DELIMITER ;
-- 调用
SELECT get_student_name('221101');
SELECT 学号, get_student_name(学号) AS 姓名 FROM xs;
计算成绩等级
sql
DELIMITER //
CREATE FUNCTION get_grade(score DECIMAL(5,2))
RETURNS VARCHAR(10)
DETERMINISTIC NO SQL
BEGIN
RETURN CASE
WHEN score >= 90 THEN '优秀'
WHEN score >= 80 THEN '良好'
WHEN score >= 70 THEN '中等'
WHEN score >= 60 THEN '及格'
ELSE '不及格'
END;
END //
DELIMITER ;
-- 调用
SELECT 学号, 课程号, 成绩, get_grade(成绩) AS 等级 FROM cj;
创建一个函数,将手机号中间四位隐藏,如 138****5678。
sql
DELIMITER //
CREATE FUNCTION mask_phone(phone CHAR(11))
RETURNS CHAR(11)
DETERMINISTIC NO SQL
BEGIN
IF phone IS NULL OR LENGTH(phone) != 11 THEN
RETURN NULL;
END IF;
RETURN CONCAT(LEFT(phone, 3), '****', RIGHT(phone, 4));
END //
DELIMITER ;
-- 使用
SELECT 姓名, mask_phone(手机号) AS 手机号 FROM xs;
游标
游标步骤
sql
声明游标 DECLARE cursor_name CURSOR FOR SELECT ...
打开游标 OPEN cursor_name
提取数据 FETCH cursor_name INTO variable_list
关闭游标 CLOSE cursor_name
声明
sql
DECLARE 游标名 CURSOR FOR SELECT语句
sql
DECLARE xs_cur1 CURSOR FOR
SELECT学号,姓名,性别,出生日期,总学分
FROM xscj
WHERE 专业= '计算机';
创建一个包含游标的存储过程,统计xs表中指定班级人数和获得最大学号。
sql
USE xscj;
DELIMITER $$
DROP PROCEDURE IF EXISTS class;
CREATE PROCEDURE class(IN bj char(4),OUT number integer,OUT xhmax char(6))
BEGIN
DECLARE xh char(6);
DECLARE found bit DEFAULT TRUE;
DECLARE number_xs CURSOR FOR
SELECT 学号 FROM xs WHERE LEFT(学号,4)=bj ORDER BY 学号;
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET found=false;
SET number=0;
OPEN number_xs;
FETCH number_xs INTO xh;
WHILE found DO
SET number=number+1;
SET xhmax= xh;
FETCH number_xs INTO xh;
END WHILE;
CLOSE number_xs;
END $$
DELIMITER ;
调用此存储过程并查看结果:
sql
SET @bj='2211';
CALL xscj.class('2211',@num,@xh);
SELECT@bj 班级, @num 人数, @xh 最大学号;
触发器
触发器的类型
触发器按触发时机 和触发事件组合分类:
| 时机 | 事件 | 说明 |
|---|---|---|
BEFORE |
INSERT |
插入前触发 |
BEFORE |
UPDATE |
更新前触发 |
BEFORE |
DELETE |
删除前触发 |
AFTER |
INSERT |
插入后触发 |
AFTER |
UPDATE |
更新后触发 |
AFTER |
DELETE |
删除后触发 |
创建触发器语法
sql
CREATE TRIGGER 触发器名
{BEFORE | AFTER} {INSERT | UPDATE | DELETE}
ON 表名
FOR EACH ROW
[FOLLOWS | PRECEDES 其他触发器名]
BEGIN
-- 触发器体
END;
查看与删除当前数据库中触发器
sql
-- 查看所有触发器
SHOW TRIGGERS;
-- 查看特定数据库的触发器
SHOW TRIGGERS FROM xscj;
-- 查看创建语句
SHOW CREATE TRIGGER 触发器名;
-- 删除触发器
DROP TRIGGER IF EXISTS 触发器名;
创建触发器,当向cj表插入一行数据时,根据成绩对xs表的总学分进行修改。如果成绩≥60分,则总学分加上该课程的学分,否则总学分不变。
sql
USE xscj;
DROP TRIGGER IF EXISTS cj_insert;
DELIMITER $$
CREATE TRIGGER cj_insert AFTER INSERT
ON cj FOR EACH ROW
BEGIN
DECLARE xf int(1);
SELECT 学分 INTO xf FROM kc WHERE 课程号=NEW.课程号;
IF new.成绩>=60 THEN
UPDATE xs SET 总学分=总学分+xf WHERE 学号=NEW.学号;
END IF;
END$$
DELIMITER;
sql
USE xscj;
INSERT INTO xs
VALUES('221199', '王大庆', '计算机', 1, '2004-08-14', 16,NULL,NULL);
SELECT 学号,姓名,总学分 FROM xs WHERE 学号='221199'; #(a)
INSERT INTO cj(学号,课程号,成绩) VALUES('221199','206',60);
SELECT 学号,姓名,总学分 FROM xs WHERE 学号='221199'; #(b)
创建一个触发器,当修改cj表中数据时,根据修改前后成绩是否大于等于60,调整xs表总学分。
sql
USE xscj;
DROP TRIGGER IF EXISTS cj_update;
DELIMITER $$
CREATE TRIGGER cj_update BEFORE UPDATE
ON cj FOR EACH ROW
BEGIN
DECLARE xf int(1);
SELECT 学分 INTO xf FROM kc WHERE 课程号=NEW.课程号;
IF OLD.成绩>=60 AND NEW.成绩<60 THEN
UPDATE xs SET 总学分=总学分-xf WHERE 学号=OLD.学号;
END IF;
IF OLD.成绩<60 AND NEW.成绩>=60 THEN
UPDATE xs SET 总学分=总学分+xf WHERE 学号=OLD.学号;
END IF;
END$$
DELIMITER ;
sql
USE xscj;
UPDATE cj SET 成绩=50 WHERE 学号='221199';
SELECT 学号,姓名, 总学分 FROM xs WHERE 学号='221199';
创建一个触发器,当删除表xs中某个学生的信息时,同时将cj表中与该学生有关的记录全部删除。
sql
USE xscj;
DROP TRIGGER IF EXISTS xs_delete;
DELIMITER $$
CREATE TRIGGER xs_delete AFTER DELETE
ON xs FOR EACH ROW
BEGIN
DELETE FROMcjWHERE 学号=OLD.学号;
END$$
DELIMITER ;
sql
SELECT * FROM cj WHERE 学号='221199'; #(a)
DELETE FROM xs WHERE 学号='221199';
SELECT * FROM cj WHERE 学号='221199';
综合示例:审计日志自动生成
创建一个完整的触发器体系,记录所有对 xs 表的增删改操作。
sql
-- 日志表
CREATE TABLE audit_log (
audit_id INT AUTO_INCREMENT PRIMARY KEY,
action CHAR(1), -- 'I' 插入, 'U' 更新, 'D' 删除
stu_id CHAR(6),
old_data JSON,
new_data JSON,
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- INSERT 触发器
DELIMITER //
CREATE TRIGGER tr_xs_insert
AFTER INSERT ON xs
FOR EACH ROW
BEGIN
INSERT INTO audit_log (action, stu_id, new_data)
VALUES ('I', NEW.学号, JSON_OBJECT('姓名', NEW.姓名, '性别', NEW.性别, '电话', NEW.电话));
END //
-- UPDATE 触发器
CREATE TRIGGER tr_xs_update
AFTER UPDATE ON xs
FOR EACH ROW
BEGIN
INSERT INTO audit_log (action, stu_id, old_data, new_data)
VALUES ('U', NEW.学号,
JSON_OBJECT('姓名', OLD.姓名, '性别', OLD.性别, '电话', OLD.电话),
JSON_OBJECT('姓名', NEW.姓名, '性别', NEW.性别, '电话', NEW.电话));
END //
-- DELETE 触发器
CREATE TRIGGER tr_xs_delete
AFTER DELETE ON xs
FOR EACH ROW
BEGIN
INSERT INTO audit_log (action, stu_id, old_data)
VALUES ('D', OLD.学号, JSON_OBJECT('姓名', OLD.姓名, '性别', OLD.性别, '电话', OLD.电话));
END //
DELIMITER ;
这样任何对 xs 表的数据变更都会自动记录,便于审计和恢复。
事件
开启调度器
sql
SET GLOBAL EVENT_SCHEDULER = TRUE;
2022年11月11日0时0分所有商品单价下调10%。
①创建表
sql
USE test;
CREATE TABLE sp
(
id int(2) NOT NULL,
price float(6.2)
);
INSERT INTO sp
VALUES(1,20.50),(2,68.00);
SELECT * FROM sp;
②创建事件
sql
USE test;
DROP EVENT IF EXISTS changePrice;
CREATE EVENT changePrice
ON SCHEDULE AT '2022-11-11'
DO
# 在商品数据库中进行调整价格的操作
update test.sp SET sp.price= ROUND(0.9*sp.price);
SET GLOBAL EVENT_SCHEDULER = TRUE;
③测试事件
sql
USE test;
SELECT * FROM sp;
从2022年开始,每年9月30日将已经毕业的学生表和成绩表记录转移到历史数据库中,至2030年结束。
① 创建与学生(xs)表和成绩(cj)表相同结构的历史表xs_ls和cj_ls。
sql
USE xscj;
CREATE TABLE xs_ls LIKE xs;
CREATE TABLE cj_ls LIKE cj;
②准备学生(xs)表和成绩(cj)表拷贝,将进校最早的学生(学号'20xxxx')根据当前系统时间,修改为毕业班学生学号,这样既可以测试事件,又不破坏原表数据记录。
sql
USE xscj;
CREATE TABLE xs_copy AS ( SELECT * FROM xs );
CREATE TABLE cj_copy AS ( SELECT * FROM cj );
UPDATE xs_copy SET 学号=CONCAT(@byyear,SUBSTR(学号,3,4)) WHERE LEFT(学号,2)='20';
UPDATE cj_copy SET 学号=CONCAT(@byyear,SUBSTR(学号,3,4)) WHERE LEFT(学号,2)='20';
③创建事件调用的存储过程,实现事件功能。
sql
USE xscj;
DELIMITER $$
DROP PROCEDURE IF EXISTS xscj.xscj_lsp;
CREATE PROCEDURE xscj.xscj_lsp( )
BEGIN
DECLARE byyear char(2);
SET byyear= cast(year(now())-2004 AS char(2));
INSERT INTO xscj.xs_ls
SELECT * FROM xscj.xs_copy WHERE left(学号,2)= byyear;
INSERT INTO xscj.cj_ls
SELECT * FROM xscj.cj_copy WHERE left(学号,2)= byyear;
DELETE FROM xscj.xs_copy WHERE left(学号,2)= byyear;
DELETE FROM xscj.cj_copy WHERE left(学号,2)= byyear;
END$$
DELIMITER ;
④创建事件
sql
USE xscj;
DROP EVENT IF EXISTS xscj_tols;
CREATE EVENT xscj_tols
ON SCHEDULE EVERY 1 YEAR
STARTS '2022-09-30'
ENDS '2030-09-30'
DO
CALL xscj.xscj_lsp();
SET GLOBAL EVENT_SCHEDULER = TRUE;
⑤测试事件
sql
USE xscj;
SET @byyear= CAST(year(now())-2004 AS char(2));
SELECT * FROM xs_copy WHERE left(学号,2)= @byyear; #(a)
SELECT * FROM cj_copy WHERE left(学号,2)= @byyear; #(b)
SELECT * FROM xs_ls WHERE left(学号,2)= @byyear; #(c)
SELECT * FROM cj_ls WHERE left(学号,2)= @byyear;
修改事件
将事件changePrice的名字改成sp_changePrice。
sql
ALTER EVENT changePrice
RENAME TO spchangePrice;
删除事件
sql
DROP EVENT [IF EXISTS][数据库名.] 事件名
表数据导出
将kc表导出,以.txt格式存储
①确认允许的路径
sql
SHOW VARIABLES LIKE 'secure_file_priv';
sql
USE xscj;
SELECT * FROM kc
INTO OUTFILE '/var/lib/mysql-files/kc.txt'
FIELDS TERMINATED BY ',' -- 逗号分隔
LINES TERMINATED BY '\n';
表数据导入
创建course表,将txt文件中存储的数据恢复到该表中,并显示过程和结果,截图上传
sql
DESC kc;
①创建course表
sql
CREATE TABLE course (
课程号 CHAR(6) PRIMARY KEY,
课程名 VARCHAR(50),
学分 INT
);
② 导入
sql
LOAD DATA INFILE '/var/lib/mysql-files/kc.txt'
INTO TABLE course
FIELDS TERMINATED BY '\t' -- 必须与导出时的分隔符一致
ENCLOSED BY '"' -- 如果导出时有包裹符
LINES TERMINATED BY '\n';
③验证
sql
SELECT * FROM course;
用户管理
添加两个新的用户,king的密码为queen,palo的密码为530415。
sql
CREATE USER
'king'@'localhost' IDENTIFIED BY 'queen',
'palo'@'localhost' IDENTIFIED BY '530415';
查看用户
sql
USE mysql;
SHOW tables;
SELECT * FROM user;
删除用户palo。
sql
DROP USER palo@localhost;
修改用户名
sql
RENAME USER '旧名'@'host' TO '新名'@'host';
将用户ken的密码修改为qen。
sql
SET PASSWORD FOR 'ken'@'localhost' = PASSWORD('qen');
权限控制
给谁? → 能做什么? → 在哪个东西上? → 有没有附加能力?
权限
1. 数据操作权限(表级 / 列级)
| 权限 | 说明 |
|---|---|
SELECT |
查询数据 |
INSERT |
插入新数据 |
UPDATE |
修改现有数据 |
DELETE |
删除数据 |
2. 结构管理权限(表级 / 数据库级)
| 权限 | 说明 |
|---|---|
CREATE |
创建数据库或表 |
DROP |
删除数据库或表 |
ALTER |
修改表结构(增删列、改类型) |
INDEX |
创建 / 删除索引 |
REFERENCES |
创建外键约束 |
3. 程序对象权限
| 权限 | 说明 |
|---|---|
CREATE VIEW |
创建视图 |
SHOW VIEW |
查看视图的定义语句 |
CREATE ROUTINE |
创建存储过程 / 函数 |
ALTER ROUTINE |
修改或删除存储过程 / 函数 |
EXECUTE |
执行存储过程 / 函数 |
TRIGGER |
创建 / 删除触发器 |
EVENT |
管理事件 |
4. 高级管理权限(通常只给管理员)
| 权限 | 说明 |
|---|---|
ALL [PRIVILEGES] |
授予除 GRANT OPTION 和 PROXY 外的所有权限 |
SUPER |
超级权限(KILL 查询、修改全局参数等) |
PROCESS |
查看所有进程(SHOW PROCESSLIST) |
RELOAD |
执行 FLUSH 操作 |
SHOW DATABASES |
列出所有数据库(默认权限) |
FILE |
使用 SELECT INTO OUTFILE、LOAD_FILE 读写文件 |
CREATE USER |
创建 / 删除 / 修改用户 |
GRANT OPTION |
允许将自己的权限授予其他用户(非常重要) |
对象层级:在哪个东西上
权限有精确的作用范围,从大到小依次是:
| 对象层级 | 写法 | 效果 |
|---|---|---|
| 全局 | ON *.* |
对所有数据库下的所有表生效 |
| 数据库 | ON 数据库名.* |
对指定库下的所有表生效 |
| 表 | ON 数据库名.表名 |
只对那张具体的表生效 |
| 列 | ON 数据库名.表名 (列1, 列2) |
只对表中指定的几列生效 |
| 存储过程/函数 | ON PROCEDURE 数据库名.过程名 或 ON FUNCTION 数据库名.函数名 |
只对该过程/函数生效 |
示例对比:
sql
-- 全局 SELECT:可以查所有库所有表(包括以后新建的库)
GRANT SELECT ON *.* TO 'reader'@'%';
-- 数据库级:只能查 xscj 库里的表,不能碰别的库
GRANT SELECT ON xscj.* TO 'reader2'@'%';
-- 表级:只能查 xscj.xs 这一张表
GRANT SELECT ON xscj.xs TO 'reader3'@'%';
-- 列级:只能查 xs 表中的姓名和学号两列
GRANT SELECT (学号, 姓名) ON xscj.xs TO 'reader4'@'%';
TO 和 FROM
TO:授权给某个用户。如果用户不存在,MySQL 5.7 及更早版本会自动创建该用户;MySQL 8.0 则必须先CREATE USER再授权(推荐先创建再授权,避免不经意创建空密码用户)。FROM:撤销权限时使用(见REVOKE)。撤销的语法结构与授权几乎对称,只是把TO换成FROM,并且GRANT OPTION需要单独回收。
WITH 选项
在授权语句末尾可以添加附加选项,控制用户权限的传递或资源限制。
1. WITH GRANT OPTION
允许该用户把自己拥有的权限再授给别人。这就像公司里你有了公章使用权,还能给下属盖章的权限。
sql
GRANT SELECT ON xscj.* TO 'manager'@'%' WITH GRANT OPTION;
manager 现在可以执行:
sql
GRANT SELECT ON xscj.* TO 'clerk'@'%';
危险 :如果给的是 ALL PRIVILEGES 和 GRANT OPTION,那个用户就成了管理员,可以任意授权甚至提升自己权限。必须严格控制。
2. 资源限制
可以限制用户每小时执行的查询、更新、连接次数,防止单个用户过度消耗资源。
sql
GRANT SELECT ON xscj.* TO 'batch'@'%'
WITH MAX_QUERIES_PER_HOUR 100 -- 每小时最多执行100次查询
MAX_UPDATES_PER_HOUR 20 -- 每小时最多更新20次
MAX_CONNECTIONS_PER_HOUR 5 -- 每小时最多连接5次
MAX_USER_CONNECTIONS 2; -- 最多允许2个同时连接
当限制达到时,该用户会被拒绝操作,直到下一个时间窗口。
授权实战场景
场景1:给后端应用一个专用账户
sql
-- 先创建用户
CREATE USER 'app'@'10.0.0.5' IDENTIFIED BY 'Str0ng!Pass';
-- 授予对业务库的常规读写权限(不授权改结构)
GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'app'@'10.0.0.5';
场景2:给数据分析师只读权限
sql
CREATE USER 'analyst'@'%' IDENTIFIED BY 'DataAn@ly1';
GRANT SELECT ON mydb.* TO 'analyst'@'%';
场景3:给临时工只查名字和邮箱
sql
GRANT SELECT (name, email) ON mydb.contacts TO 'temp'@'localhost';
场景4:给 DBA 大权限,但要限制远程来源
sql
CREATE USER 'dba'@'192.168.1.50' IDENTIFIED BY 'DbaAdmin#2025';
GRANT ALL ON *.* TO 'dba'@'192.168.1.50' WITH GRANT OPTION;
绝不把 ALL 和 GRANT OPTION 给 '%' 主机。
查看已授的权限
sql
-- 查看某个用户的所有权限
SHOW GRANTS FOR 'app'@'10.0.0.5';
-- 查看当前登录用户自己的权限
SHOW GRANTS;
授予用户ken对xs表的SELECT权限。
sql
USE xscj;
GRANT SELECT
ON xs
TO ken@localhost;
授予liu和zhang新用户对xs表的SELECT和UPDATE权限。
sql
GRANT SELECT,UPDATE
ON xs
TO liu@localhost IDENTIFIED BY 'pwd123',
zhang@localhost IDENTIFIED BY 'pwd123';
授予用户ken对xs表的姓名列和学号列的UPDATE权限。
sql
USE xscj;
GRANT UPDATE(姓名,学号)
ON xs
TO ken@localhost;
授予用户ken对xscj数据库中的所有表的SELECT权限。
sql
GRANT SELECT
ON xscj.*
TO ken@localhost;
授予用户ken对xscj数据库的所有数据库权限。
sql
USE xscj;
GRANT ALL
ON *
TO ken@localhost;
授予用户peter对所有数据库中的所有表的CREATE、ALTERT和DROP权限。
sql
GRANT CREATE,ALTER,DROP
ON *.*
TO peter@localhost IDENTIFIED BY 'p123';
授予用户peter创建新用户的权限。
sql
GRANT CREATE USER
ON *.*
TO peter@localhost;
回收用户caddy对xs表的SELECT权限。
sql
USE xscj;
REVOKE SELECT
ON xs
FROM caddy@localhost;
回收用户转授权限的能力
sql
REVOKE GRANT OPTION ON 对象层级 FROM '用户名'@'主机';REVOKE GRANT OPTION ON 对象层级 FROM '用户名'@'主机';