MySQL SQL语法详解:带注释的实用示例

一、DDL(数据定义语言)详细示例

1. 数据库操作

sql 复制代码
-- 创建数据库,指定字符集和排序规则
CREATE DATABASE IF NOT EXISTS company_db 
CHARACTER SET utf8mb4          -- 使用UTF-8编码,支持表情符号
COLLATE utf8mb4_unicode_ci;    -- 不区分大小写的Unicode排序规则

-- 查看所有数据库
SHOW DATABASES;

-- 选择要使用的数据库
USE company_db;

-- 删除数据库(危险操作,谨慎使用)
-- DROP DATABASE IF EXISTS old_db;

2. 表操作完整示例

sql 复制代码
-- 创建员工表
CREATE TABLE IF NOT EXISTS employees (
    -- 列定义开始
    id INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '员工ID,主键',
    emp_no VARCHAR(20) NOT NULL UNIQUE COMMENT '员工工号,唯一',
    name VARCHAR(50) NOT NULL COMMENT '员工姓名',
    gender ENUM('M', 'F') NOT NULL DEFAULT 'M' COMMENT '性别:M-男,F-女',
    birth_date DATE NOT NULL COMMENT '出生日期',
    department_id INT UNSIGNED COMMENT '部门ID,外键',
    salary DECIMAL(10, 2) NOT NULL DEFAULT 0.00 COMMENT '月薪,10位数字,2位小数',
    hire_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入职时间',
    status TINYINT NOT NULL DEFAULT 1 COMMENT '状态:1-在职,0-离职',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    
    -- 主键约束
    PRIMARY KEY (id),
    
    -- 唯一约束
    UNIQUE KEY uk_emp_no (emp_no),
    
    -- 外键约束(引用部门表)
    CONSTRAINT fk_employee_department 
    FOREIGN KEY (department_id) 
    REFERENCES departments(id) 
    ON DELETE SET NULL           -- 部门删除时设为NULL
    ON UPDATE CASCADE,           -- 部门ID更新时级联更新
    
    -- 普通索引
    INDEX idx_department (department_id),
    INDEX idx_name (name),
    INDEX idx_hire_date (hire_date),
    
    -- 复合索引
    INDEX idx_dept_status (department_id, status),
    
    -- 检查约束(MySQL 8.0.16+)
    CONSTRAINT chk_salary CHECK (salary >= 0),
    CONSTRAINT chk_birth_date CHECK (birth_date < '2005-01-01')
    
    -- 表选项
) ENGINE = InnoDB                     -- 使用InnoDB存储引擎
  DEFAULT CHARSET = utf8mb4           -- 默认字符集
  COLLATE = utf8mb4_unicode_ci        -- 排序规则
  AUTO_INCREMENT = 1001               -- 自增起始值
  COMMENT = '员工信息表'              -- 表注释
  ROW_FORMAT = DYNAMIC;               -- 行格式

3. 修改表结构

sql 复制代码
-- 添加新列
ALTER TABLE employees 
ADD COLUMN email VARCHAR(100) 
AFTER name                          -- 在name列之后添加
COMMENT '邮箱地址';

-- 修改列定义
ALTER TABLE employees 
MODIFY COLUMN salary DECIMAL(12, 2)  -- 修改数据类型
NOT NULL DEFAULT 0.00
COMMENT '月薪(扩大范围)';

-- 修改列名
ALTER TABLE employees 
CHANGE COLUMN birth_date birth_day DATE  -- birth_date改为birth_day
COMMENT '出生日';

-- 删除列(危险操作)
-- ALTER TABLE employees DROP COLUMN temp_column;

-- 添加索引
ALTER TABLE employees 
ADD INDEX idx_email (email(50));     -- 对email前50个字符建索引

-- 添加唯一索引
ALTER TABLE employees 
ADD UNIQUE INDEX uk_email (email);

-- 删除索引
ALTER TABLE employees 
DROP INDEX idx_email;

-- 添加外键
ALTER TABLE employees 
ADD CONSTRAINT fk_employee_title 
FOREIGN KEY (title_id) 
REFERENCES job_titles(id);

-- 删除外键
ALTER TABLE employees 
DROP FOREIGN KEY fk_employee_title;

-- 重命名表
RENAME TABLE old_employees TO archived_employees;

-- 或者使用ALTER TABLE重命名
ALTER TABLE employees RENAME TO staff;

4. 索引管理

sql 复制代码
-- 创建普通索引
CREATE INDEX idx_salary ON employees(salary);

-- 创建唯一索引
CREATE UNIQUE INDEX idx_employee_email ON employees(email);

-- 创建前缀索引(节省空间)
CREATE INDEX idx_name_prefix ON employees(name(10));  -- 只索引前10个字符

-- 创建全文索引(用于文本搜索)
CREATE TABLE articles (
    id INT,
    title VARCHAR(200),
    content TEXT,
    FULLTEXT idx_fulltext (title, content)  -- 全文索引
) ENGINE = InnoDB;

-- 查看表索引
SHOW INDEX FROM employees;

-- 删除索引
DROP INDEX idx_salary ON employees;

5. 视图操作

sql 复制代码
-- 创建视图(虚拟表)
CREATE OR REPLACE VIEW v_active_employees AS
SELECT 
    id,
    emp_no,
    name,
    department_id,
    salary,
    hire_date
FROM employees
WHERE status = 1                     -- 只显示在职员工
  AND salary > 5000                  -- 薪资大于5000
WITH CHECK OPTION;                   -- 保证视图数据一致性

-- 使用视图查询
SELECT * FROM v_active_employees 
WHERE department_id = 1;

-- 修改视图
ALTER VIEW v_active_employees AS
SELECT * FROM employees 
WHERE status = 1;

-- 删除视图
DROP VIEW IF EXISTS v_active_employees;

二、DML(数据操作语言)详细示例

1. INSERT 插入数据

sql 复制代码
-- 基本插入
INSERT INTO employees (emp_no, name, gender, birth_date, salary)
VALUES ('E1001', '张三', 'M', '1990-05-15', 8000.00);

-- 插入多行数据(高效方式)
INSERT INTO employees (emp_no, name, gender, birth_date, salary) 
VALUES 
    ('E1002', '李四', 'M', '1992-08-20', 7500.00),
    ('E1003', '王芳', 'F', '1993-03-10', 8200.00),
    ('E1004', '赵六', 'M', '1991-11-25', 9000.00);

-- 插入查询结果
INSERT INTO employee_archive (emp_no, name, salary, archive_date)
SELECT emp_no, name, salary, NOW() 
FROM employees 
WHERE status = 0;                    -- 插入离职员工到归档表

-- INSERT IGNORE:忽略重复键错误
INSERT IGNORE INTO employees (emp_no, name)
VALUES ('E1001', '张三重复');        -- 如果E1001已存在,则忽略

-- INSERT ... ON DUPLICATE KEY UPDATE
-- 存在则更新,不存在则插入
INSERT INTO daily_stats (stat_date, page_views, unique_visitors)
VALUES ('2024-01-15', 100, 50)
ON DUPLICATE KEY UPDATE 
    page_views = page_views + VALUES(page_views),     -- 累加
    unique_visitors = VALUES(unique_visitors);        -- 替换

-- REPLACE:存在则删除后插入(注意:删除原有记录)
REPLACE INTO employees (id, emp_no, name)
VALUES (1, 'E1001', '张三新名字');    -- 如果id=1存在,先删除再插入

2. UPDATE 更新数据

sql 复制代码
-- 基本更新
UPDATE employees 
SET salary = salary * 1.1,           -- 涨薪10%
    updated_at = NOW()                -- 更新修改时间
WHERE department_id = 1               -- 条件:部门ID=1
  AND hire_date < '2023-01-01';       -- 且入职早于2023-01-01

-- 使用子查询更新
UPDATE employees e
SET e.salary = (
    SELECT AVG(salary) * 1.2 
    FROM employees 
    WHERE department_id = e.department_id
)
WHERE e.performance_rating = 'A';     -- 绩效A的员工涨到部门平均的1.2倍

-- 多表更新(更新关联表)
UPDATE employees e
JOIN departments d ON e.department_id = d.id
SET e.salary = e.salary + 1000,       -- 员工加薪
    d.budget = d.budget - 1000        -- 部门预算减少
WHERE d.name = '技术部';

-- 使用CASE语句条件更新
UPDATE employees 
SET salary = CASE
    WHEN performance_rating = 'A' THEN salary * 1.2      -- A级涨20%
    WHEN performance_rating = 'B' THEN salary * 1.1      -- B级涨10%
    WHEN performance_rating = 'C' THEN salary * 1.05     -- C级涨5%
    ELSE salary                                          -- 其他不变
END,
bonus = CASE
    WHEN years_of_service > 10 THEN 5000                -- 工龄>10年
    WHEN years_of_service > 5 THEN 3000                 -- 工龄>5年
    ELSE 1000                                           -- 其他
END;

-- LIMIT限制更新数量(防止误操作)
UPDATE employees 
SET status = 0                        -- 标记为离职
WHERE last_work_date < '2023-12-01'   -- 最后工作日早于
ORDER BY id                           -- 按ID排序
LIMIT 10;                             -- 只更新10条

3. DELETE 删除数据

sql 复制代码
-- 基本删除(危险!一定要有条件)
DELETE FROM employees 
WHERE status = 0                       -- 删除离职员工
  AND updated_at < '2023-01-01';       -- 且长时间未更新

-- 使用子查询删除
DELETE FROM employees 
WHERE department_id IN (
    SELECT id FROM departments 
    WHERE status = 'closed'            -- 删除已关闭部门的员工
);

-- 关联删除
DELETE e FROM employees e
JOIN departments d ON e.department_id = d.id
WHERE d.location = '北京旧办公室';      -- 删除旧办公室员工

-- LIMIT限制删除数量(安全做法)
DELETE FROM audit_log 
WHERE created_at < DATE_SUB(NOW(), INTERVAL 90 DAY)  -- 90天前的日志
ORDER BY created_at ASC               -- 从最早的开始删
LIMIT 1000;                           -- 每次最多删1000条

-- TRUNCATE vs DELETE
TRUNCATE TABLE temp_data;              -- 快速清空,重置自增ID,不可回滚
-- DELETE FROM temp_data;               -- 逐行删除,可回滚,保留自增ID

-- 删除重复数据
DELETE t1 FROM employees t1
INNER JOIN employees t2 
WHERE t1.id < t2.id                    -- 保留ID大的(或小的)
  AND t1.emp_no = t2.emp_no;           -- emp_no相同即为重复

4. 事务处理

sql 复制代码
-- 开始事务
START TRANSACTION;
-- 或者:BEGIN;

-- 银行转账示例
START TRANSACTION;

-- 第一步:从账户1扣款
UPDATE accounts 
SET balance = balance - 1000,
    update_time = NOW()
WHERE account_no = 'A001' 
  AND balance >= 1000;                 -- 余额检查

-- 检查是否成功
SELECT ROW_COUNT() INTO @rows_affected;  -- 获取影响行数

IF @rows_affected = 1 THEN
    -- 第二步:向账户2存款
    UPDATE accounts 
    SET balance = balance + 1000,
        update_time = NOW()
    WHERE account_no = 'A002';
    
    -- 第三步:记录交易流水
    INSERT INTO transactions (from_account, to_account, amount, type)
    VALUES ('A001', 'A002', 1000, 'TRANSFER');
    
    -- 提交事务
    COMMIT;
    SELECT '转账成功' AS result;
ELSE
    -- 回滚事务(余额不足)
    ROLLBACK;
    SELECT '转账失败:余额不足' AS result;
END IF;

-- 设置保存点(部分回滚)
START TRANSACTION;

INSERT INTO orders (order_no, amount) VALUES ('O001', 100);
SAVEPOINT sp1;                         -- 设置保存点

UPDATE inventory SET stock = stock - 1 WHERE product_id = 1;

-- 如果库存更新失败,可以回滚到保存点
-- ROLLBACK TO SAVEPOINT sp1;          -- 只回滚库存更新,保留订单

INSERT INTO payments (order_no, amount) VALUES ('O001', 100);

COMMIT;  -- 提交所有操作

-- 查看事务状态
SHOW VARIABLES LIKE 'autocommit';      -- 查看是否自动提交
SET autocommit = 0;                    -- 关闭自动提交(慎用)

三、DQL(数据查询语言)详细示例

1. 基础查询

sql 复制代码
-- 查询所有列(生产环境慎用)
SELECT * FROM employees;

-- 查询特定列
SELECT 
    id,
    name AS 姓名,                     -- 使用别名
    salary AS 月薪,
    salary * 12 AS 年薪               -- 计算列
FROM employees;

-- 条件查询
SELECT name, department, salary
FROM employees
WHERE department = '技术部'            -- 等于
  AND salary > 8000                   -- 大于
  AND hire_date BETWEEN '2022-01-01' AND '2023-12-31'  -- 范围
  AND name LIKE '张%'                 -- 模糊匹配:张开头
  AND (bonus IS NULL OR bonus = 0)    -- NULL检查
  AND status IN (1, 2, 3);            -- IN列表

-- 去重查询
SELECT DISTINCT department FROM employees;

-- 排序
SELECT name, salary, hire_date
FROM employees
WHERE status = 1
ORDER BY 
    department ASC,                   -- 先按部门升序
    salary DESC,                      -- 再按薪资降序
    hire_date ASC;                    -- 最后按入职日期升序

-- 限制结果集
SELECT name, salary
FROM employees
ORDER BY salary DESC
LIMIT 10;                             -- 前10条(高薪榜)

-- 分页查询
SELECT name, salary
FROM employees
ORDER BY id
LIMIT 20 OFFSET 40;                   -- 每页20条,第3页(跳过前40条)
-- 等价于:LIMIT 40, 20

2. 聚合函数

sql 复制代码
-- 基本聚合
SELECT 
    COUNT(*) AS 总人数,                -- 统计行数
    COUNT(DISTINCT department) AS 部门数, -- 去重统计
    AVG(salary) AS 平均薪资,           -- 平均值
    MAX(salary) AS 最高薪资,           -- 最大值
    MIN(salary) AS 最低薪资,           -- 最小值
    SUM(salary) AS 薪资总额            -- 求和
FROM employees
WHERE status = 1;

-- 分组聚合
SELECT 
    department AS 部门,
    COUNT(*) AS 人数,
    AVG(salary) AS 平均薪资,
    MAX(salary) AS 部门最高薪资,
    SUM(salary) AS 部门薪资总额
FROM employees
WHERE status = 1
GROUP BY department                    -- 按部门分组
HAVING AVG(salary) > 10000             -- 分组后过滤(平均薪资>1万)
ORDER BY AVG(salary) DESC;             -- 按平均薪资降序

-- 分组聚合扩展
SELECT 
    department,
    gender,
    COUNT(*) AS 人数,
    AVG(salary) AS 平均薪资
FROM employees
GROUP BY department, gender           -- 多列分组
WITH ROLLUP;                          -- 添加小计和总计行

3. 多表连接查询

sql 复制代码
-- 内连接(INNER JOIN) - 只返回匹配的行
SELECT 
    e.name AS 员工姓名,
    d.name AS 部门名称,
    p.name AS 项目名称
FROM employees e
INNER JOIN departments d ON e.department_id = d.id        -- 员工-部门
INNER JOIN projects p ON e.project_id = p.id              -- 员工-项目
WHERE d.status = 'active'
  AND p.status = 'ongoing';

-- 左连接(LEFT JOIN) - 返回左表所有行
SELECT 
    e.name AS 员工姓名,
    d.name AS 部门名称
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id         -- 即使没部门的员工也显示
WHERE e.status = 1;

-- 右连接(RIGHT JOIN) - 返回右表所有行
SELECT 
    d.name AS 部门名称,
    COUNT(e.id) AS 员工数
FROM departments d
RIGHT JOIN employees e ON d.id = e.department_id          -- 显示所有员工,即使没对应部门
GROUP BY d.name;

-- 全外连接(MySQL通过UNION实现)
SELECT e.name, d.name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id
UNION                                                   -- 合并结果,去重
SELECT e.name, d.name
FROM employees e
RIGHT JOIN departments d ON e.department_id = d.id
WHERE e.id IS NULL;                                     -- 只取右表独有的

-- 自连接(同一表内连接)
SELECT 
    e1.name AS 员工姓名,
    e2.name AS 上级领导,
    e1.salary AS 员工薪资,
    e2.salary AS 领导薪资
FROM employees e1
LEFT JOIN employees e2 ON e1.manager_id = e2.id         -- 连接自己的经理
WHERE e1.salary > e2.salary;                            -- 薪资比领导高的员工

4. 子查询

sql 复制代码
-- 标量子查询(返回单个值)
SELECT name, salary,
    (SELECT AVG(salary) FROM employees) AS 公司平均薪资,
    salary - (SELECT AVG(salary) FROM employees) AS 与平均的差距
FROM employees;

-- 列子查询(返回一列)
SELECT name, department, salary
FROM employees
WHERE department_id IN (
    SELECT id FROM departments 
    WHERE location = '北京'                             -- 在北京的部门
);

-- 行子查询(返回一行)
SELECT name, salary
FROM employees
WHERE (department_id, salary) = (                       -- 同时匹配部门和薪资
    SELECT department_id, MAX(salary) 
    FROM employees 
    WHERE department_id = 1
);

-- 表子查询(返回多行多列)
SELECT dept_stats.*
FROM (
    SELECT 
        department,
        COUNT(*) as emp_count,
        AVG(salary) as avg_salary
    FROM employees
    GROUP BY department
) AS dept_stats                                         -- 必须使用别名
WHERE dept_stats.avg_salary > 10000;

-- EXISTS / NOT EXISTS
SELECT d.name AS 部门名称
FROM departments d
WHERE EXISTS (                                          -- 存在员工的部门
    SELECT 1 FROM employees e 
    WHERE e.department_id = d.id 
      AND e.salary > 20000
);

-- 相关子查询(引用外部查询)
SELECT e1.name, e1.salary, e1.department
FROM employees e1
WHERE e1.salary > (
    SELECT AVG(e2.salary) 
    FROM employees e2 
    WHERE e2.department = e1.department                 -- 引用外部查询的部门
);

5. 高级查询技巧

sql 复制代码
-- CASE表达式
SELECT 
    name,
    salary,
    CASE 
        WHEN salary >= 20000 THEN '高薪'
        WHEN salary >= 10000 THEN '中薪'
        WHEN salary >= 5000 THEN '普薪'
        ELSE '低薪'
    END AS 薪资等级,
    CASE department
        WHEN '技术部' THEN '技术序列'
        WHEN '销售部' THEN '业务序列'
        WHEN '人事部' THEN '职能序列'
        ELSE '其他序列'
    END AS 序列类型
FROM employees;

-- 窗口函数(MySQL 8.0+)
SELECT 
    name,
    department,
    salary,
    ROW_NUMBER() OVER (ORDER BY salary DESC) AS 公司排名,      -- 行号
    RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS 部门排名, -- 排名(会跳过)
    DENSE_RANK() OVER (ORDER BY salary DESC) AS 密集排名,      -- 排名(不跳过)
    NTILE(4) OVER (ORDER BY salary DESC) AS 薪资四分位,        -- 分桶
    LAG(salary, 1) OVER (ORDER BY hire_date) AS 上月薪资,      -- 上一行
    LEAD(salary, 1) OVER (ORDER BY hire_date) AS 下月薪资,     -- 下一行
    FIRST_VALUE(salary) OVER (PARTITION BY department ORDER BY hire_date) AS 部门最早薪资,
    LAST_VALUE(salary) OVER (PARTITION BY department ORDER BY hire_date ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS 部门最新薪资,
    SUM(salary) OVER (PARTITION BY department) AS 部门薪资总额, -- 累计
    AVG(salary) OVER (ORDER BY hire_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS 最近三月平均 -- 滑动平均
FROM employees;

-- 公用表表达式CTE(MySQL 8.0+)
WITH RECURSIVE cte_org AS (
    -- 锚点查询(起始点)
    SELECT 
        id, 
        name, 
        parent_id, 
        1 AS level,
        CAST(id AS CHAR(100)) AS path
    FROM organization
    WHERE parent_id IS NULL           -- 顶层节点
    
    UNION ALL
    
    -- 递归查询
    SELECT 
        o.id, 
        o.name, 
        o.parent_id, 
        c.level + 1,
        CONCAT(c.path, '->', o.id)
    FROM organization o
    INNER JOIN cte_org c ON o.parent_id = c.id
)
SELECT * FROM cte_org ORDER BY path;

-- JSON函数(MySQL 5.7+)
SELECT 
    id,
    name,
    JSON_EXTRACT(profile, '$.skills') AS 技能,          -- 提取JSON字段
    JSON_CONTAINS(profile, '"MySQL"', '$.skills') AS 会MySQL, -- 检查包含
    JSON_LENGTH(profile, '$.projects') AS 项目数量       -- JSON数组长度
FROM employees
WHERE JSON_VALID(profile) = 1;                          -- 验证JSON格式

6. 联合查询

sql 复制代码
-- UNION:合并结果,去重
SELECT '在职' AS 类型, COUNT(*) AS 人数 FROM employees WHERE status = 1
UNION
SELECT '离职' AS 类型, COUNT(*) AS 人数 FROM employees WHERE status = 0
UNION
SELECT '总计' AS 类型, COUNT(*) AS 人数 FROM employees;

-- UNION ALL:合并结果,不去重(更快)
SELECT name, salary FROM current_employees
UNION ALL
SELECT name, salary FROM former_employees
ORDER BY salary DESC;

-- 使用UNION实现全外连接
SELECT e.name, d.name
FROM employees e LEFT JOIN departments d ON e.department_id = d.id
UNION
SELECT e.name, d.name
FROM employees e RIGHT JOIN departments d ON e.department_id = d.id
WHERE e.id IS NULL;

7. 查询优化和技巧

sql 复制代码
-- 使用EXPLAIN分析查询
EXPLAIN 
SELECT e.name, d.name, p.name
FROM employees e
JOIN departments d ON e.department_id = d.id
JOIN projects p ON e.project_id = p.id
WHERE e.salary > 10000
  AND d.location = '北京'
ORDER BY e.hire_date;

-- 使用索引提示
SELECT /*+ INDEX(e idx_salary) */ e.name, e.salary
FROM employees e FORCE INDEX (idx_salary)  -- 强制使用索引
WHERE e.salary BETWEEN 5000 AND 15000;

-- 避免SELECT *
SELECT id, name, email FROM users;  -- 只查询需要的列

-- 使用覆盖索引
-- 创建索引覆盖所有查询字段
CREATE INDEX idx_covering ON employees(department_id, status, salary);

-- 然后查询时可以直接从索引获取数据
SELECT department_id, COUNT(*), AVG(salary)
FROM employees
WHERE status = 1
GROUP BY department_id;  -- 可以使用覆盖索引

-- 分页优化
-- 不好的分页(偏移量大时慢)
SELECT * FROM large_table LIMIT 1000000, 20;

-- 优化的分页(使用游标)
SELECT * FROM large_table 
WHERE id > 1000000  -- 记住上一页最后一条的ID
ORDER BY id 
LIMIT 20;

-- 使用派生表延迟关联
SELECT * 
FROM employees e
INNER JOIN (
    SELECT id 
    FROM employees 
    WHERE department_id = 1 
    ORDER BY hire_date 
    LIMIT 100000, 20  -- 先在子查询中分页
) AS tmp ON e.id = tmp.id;

四、DCL(数据控制语言)详细示例

1. 用户管理

sql 复制代码
-- 创建用户
CREATE USER 'app_user'@'localhost' 
IDENTIFIED BY 'StrongPass123!'          -- 密码
PASSWORD EXPIRE INTERVAL 90 DAY         -- 90天过期
FAILED_LOGIN_ATTEMPTS 5                 -- 最大失败尝试次数
PASSWORD_LOCK_TIME 1;                   -- 锁定1天

-- 创建用户指定认证插件
CREATE USER 'legacy_user'@'%'
IDENTIFIED WITH mysql_native_password   -- 使用旧版认证
BY 'OldStylePass';

-- 修改用户
ALTER USER 'app_user'@'localhost'
IDENTIFIED BY 'NewPass456!'            -- 修改密码
PASSWORD EXPIRE NEVER                   -- 密码永不过期
ACCOUNT LOCK;                           -- 锁定账号

ALTER USER 'app_user'@'localhost'
ACCOUNT UNLOCK;                         -- 解锁账号

-- 重命名用户
RENAME USER 'old_user'@'localhost' 
TO 'new_user'@'localhost';

-- 删除用户
DROP USER IF EXISTS 'test_user'@'%';

-- 查看用户
SELECT User, Host, account_locked, password_expired
FROM mysql.user
WHERE User LIKE '%app%';                -- 查找包含app的用户

2. 权限管理

sql 复制代码
-- 授予数据库级别权限
GRANT 
    SELECT, INSERT, UPDATE, DELETE,    -- 数据操作权限
    CREATE, ALTER, DROP,               -- 结构操作权限  
    INDEX,                             -- 索引权限
    CREATE VIEW, SHOW VIEW             -- 视图权限
ON company_db.*                        -- 所有表
TO 'dev_user'@'localhost';

-- 授予表级别权限
GRANT 
    SELECT (id, name, email),          -- 列级别权限(只允许查特定列)
    INSERT (name, email),              -- 只允许插入特定列
    UPDATE (name, email)               -- 只允许更新特定列
ON company_db.users                    -- 指定表
TO 'api_user'@'192.168.1.%';           -- 指定IP段

-- 授予存储过程权限
GRANT EXECUTE 
ON PROCEDURE company_db.calculate_bonus
TO 'finance_user'@'localhost';

-- 授予代理权限
GRANT PROXY 
ON 'manager'@'localhost'               -- 被代理用户
TO 'assistant'@'localhost';            -- 代理用户

-- 授予WITH GRANT OPTION权限
GRANT SELECT 
ON company_db.salary_data 
TO 'hr_manager'@'localhost'
WITH GRANT OPTION;                     -- 允许该用户将此权限授予他人

-- 授予角色
CREATE ROLE 'read_only_role';
GRANT SELECT ON *.* TO 'read_only_role';

CREATE USER 'reporter1'@'localhost';
GRANT 'read_only_role' TO 'reporter1'@'localhost';
SET DEFAULT ROLE 'read_only_role' TO 'reporter1'@'localhost';

-- 撤销权限
REVOKE DELETE 
ON company_db.* 
FROM 'dev_user'@'localhost';

REVOKE ALL PRIVILEGES, GRANT OPTION    -- 撤销所有权限和授权权限
ON *.* 
FROM 'misbehaving_user'@'%';

-- 撤销角色
REVOKE 'read_only_role' 
FROM 'reporter1'@'localhost';

-- 刷新权限(使更改立即生效)
FLUSH PRIVILEGES;

-- 查看权限
-- 查看当前用户权限
SHOW GRANTS;

-- 查看特定用户权限
SHOW GRANTS FOR 'app_user'@'localhost';

-- 查看用户拥有的角色
SHOW GRANTS FOR 'reporter1'@'localhost' USING 'read_only_role';

-- 查看mysql系统表中的权限
SELECT * 
FROM mysql.user 
WHERE user = 'app_user'\G             -- \G表示垂直显示结果

3. 权限层级示例

sql 复制代码
-- 1. 全局权限(所有数据库的所有表)
GRANT ALL PRIVILEGES ON *.* 
TO 'global_admin'@'localhost' 
WITH GRANT OPTION;

-- 2. 数据库级别权限(特定数据库的所有表)
GRANT ALL PRIVILEGES ON company_db.* 
TO 'db_admin'@'localhost';

-- 3. 表级别权限(特定表)
GRANT SELECT, INSERT, UPDATE, DELETE 
ON company_db.employees 
TO 'hr_staff'@'localhost';

-- 4. 列级别权限(特定列)
GRANT SELECT (id, name, department) 
ON company_db.employees 
TO 'auditor'@'localhost';

GRANT UPDATE (name, email) 
ON company_db.users 
TO 'operator'@'localhost';

-- 5. 存储程序权限
GRANT EXECUTE 
ON FUNCTION company_db.calculate_age 
TO 'analyst'@'localhost';

GRANT EXECUTE 
ON PROCEDURE company_db.monthly_report 
TO 'manager'@'localhost';

-- 6. 代理权限
GRANT PROXY 
ON 'admin_user'@'localhost' 
TO 'assistant'@'localhost';

4. 安全最佳实践

sql 复制代码
-- 1. 遵循最小权限原则
CREATE USER 'web_app'@'app_server_ip' 
IDENTIFIED BY '加密密码';

-- 只授予必要权限
GRANT SELECT, INSERT, UPDATE, DELETE 
ON app_db.* 
TO 'web_app'@'app_server_ip';

-- 2. 使用角色管理权限
-- 创建角色
CREATE ROLE 'app_read_only';
CREATE ROLE 'app_read_write';

-- 为角色授权
GRANT SELECT ON app_db.* TO 'app_read_only';
GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.* TO 'app_read_write';

-- 将角色分配给用户
CREATE USER 'user1'@'%' IDENTIFIED BY 'pass1';
CREATE USER 'user2'@'%' IDENTIFIED BY 'pass2';

GRANT 'app_read_only' TO 'user1'@'%';
GRANT 'app_read_write' TO 'user2'@'%';

-- 激活角色
SET DEFAULT ROLE ALL TO 'user1'@'%';

-- 3. 定期审计权限
-- 查看所有有SUPER权限的用户
SELECT User, Host 
FROM mysql.user 
WHERE Super_priv = 'Y';

-- 查看有GRANT权限的用户
SELECT User, Host 
FROM mysql.user 
WHERE Grant_priv = 'Y';

-- 查看可以创建用户的账号
SELECT User, Host 
FROM mysql.user 
WHERE Create_user_priv = 'Y';

-- 4. 密码策略管理
-- 设置密码策略(MySQL 8.0+)
SET GLOBAL validate_password.policy = STRONG;          -- 强密码策略
SET GLOBAL validate_password.length = 12;              -- 最小长度12
SET GLOBAL validate_password.mixed_case_count = 2;     -- 至少2个大小写字母
SET GLOBAL validate_password.number_count = 2;         -- 至少2个数字
SET GLOBAL validate_password.special_char_count = 1;   -- 至少1个特殊字符

-- 5. 清理无用账户
-- 查找长时间未使用的账户
SELECT User, Host, password_last_changed
FROM mysql.user
WHERE password_last_changed < DATE_SUB(NOW(), INTERVAL 180 DAY)
   OR (account_locked = 'Y' AND User NOT IN ('root', 'mysql.sys'));

-- 删除测试账户
DROP USER IF EXISTS 'test'@'%', 'demo'@'localhost';

-- 6. 安全连接要求
-- 要求SSL连接
CREATE USER 'secure_user'@'%' 
IDENTIFIED BY 'password'
REQUIRE SSL;                                           -- 必须使用SSL

-- 要求X509证书
CREATE USER 'vip_user'@'%'
IDENTIFIED BY 'password'
REQUIRE X509;                                          -- 需要客户端证书

-- 指定允许的IP段
CREATE USER 'internal_user'@'192.168.1.%'              -- 只允许内网IP段
IDENTIFIED BY 'password';

5. 权限问题排查

sql 复制代码
-- 查看当前会话权限
SELECT CURRENT_USER();                                 -- 当前连接用户
SHOW GRANTS;                                           -- 当前用户权限

-- 检查是否有特定权限
SELECT 
    * 
FROM information_schema.user_privileges 
WHERE grantee LIKE '%app_user%';

-- 检查表权限
SELECT 
    grantee,
    table_schema,
    table_name,
    privilege_type
FROM information_schema.table_privileges 
WHERE table_name = 'employees'
  AND grantee LIKE '%user%';

-- 检查列权限
SELECT 
    grantee,
    table_schema,
    table_name,
    column_name,
    privilege_type
FROM information_schema.column_privileges 
WHERE table_name = 'employees';

-- 模拟其他用户权限
-- 首先要有PROXY权限
GRANT PROXY ON 'target_user'@'localhost' TO CURRENT_USER();

-- 然后可以模拟
SET ROLE 'target_user'@'localhost';
SHOW GRANTS;  -- 现在看到的是target_user的权限

-- 重置
SET ROLE NONE;

五、SQL执行计划和优化

sql 复制代码
-- 查看执行计划
EXPLAIN FORMAT=TREE   -- 树形格式(MySQL 8.0+)
SELECT * FROM employees 
WHERE department_id = 1 
  AND salary > 10000;

EXPLAIN ANALYZE        -- 实际执行分析(MySQL 8.0.18+)
SELECT * FROM employees 
WHERE department_id = 1 
  AND salary > 10000;

-- 执行计划各列含义:
-- id: 查询序号
-- select_type: 查询类型(SIMPLE, PRIMARY, SUBQUERY等)
-- table: 访问的表
-- partitions: 匹配的分区
-- type: 访问类型(ALL, index, range, ref等)
-- possible_keys: 可能使用的索引
-- key: 实际使用的索引
-- key_len: 索引长度
-- ref: 与索引比较的列
-- rows: 预估需要检查的行数
-- filtered: 按条件过滤的行百分比
-- Extra: 额外信息

-- 强制使用或不使用索引
SELECT * FROM employees USE INDEX (idx_department) WHERE department_id = 1;
SELECT * FROM employees IGNORE INDEX (idx_department) WHERE department_id = 1;
SELECT * FROM employees FORCE INDEX (idx_department) WHERE department_id = 1;

-- 查询缓存(MySQL 8.0已移除,但旧版本可用)
SHOW VARIABLES LIKE 'query_cache%';
SET GLOBAL query_cache_size = 1000000;

总结

通过以上详细的注释和示例,我们可以看到:

  1. DDL 关注数据库结构,操作要谨慎,生产环境需要在线DDL工具
  2. DML 关注数据内容,要使用事务保证一致性,注意WHERE条件
  3. DQL 使用最频繁,需要优化查询性能,合理使用索引
  4. DCL 关注安全性,要遵循最小权限原则,定期审计

每种SQL类型都有其特定的语法和最佳实践。掌握这些细节,能够帮助你写出更高效、更安全、更易维护的数据库代码。记住:理解原理,实践验证,持续优化是成为SQL高手的必经之路。

相关推荐
fanruitian8 小时前
Springboot项目父子工程
java·数据库·spring boot
hboot8 小时前
别再被 TS 类型冲突折磨了!一文搞懂类型合并规则
前端·typescript
在西安放羊的牛油果8 小时前
浅谈 import.meta.env 和 process.env 的区别
前端·vue.js·node.js
鹏北海8 小时前
从弹窗变胖到 npm 依赖管理:一次完整的问题排查记录
前端·npm·node.js
布列瑟农的星空8 小时前
js中的using声明
前端
薛定谔的猫28 小时前
Cursor 系列(2):使用心得
前端·ai编程·cursor
用户904706683578 小时前
后端问前端:我的接口请求花了多少秒?为啥那么慢,是你慢还是我慢?
前端
深念Y8 小时前
仿B站项目 前端 4 首页 顶层导航栏
前端·vue·ai编程·导航栏·bilibili·ai开发
dragonZhang8 小时前
基于 Agent Skills 的 UI 重构实践:从 Demo 到主题化界面的升级之路
前端·ai编程·claude
super_lzb8 小时前
mybatis拦截器ParameterHandler详解
java·数据库·spring boot·spring·mybatis