MySQL查询优化:从执行计划到性能调优

引言 “过早优化是万恶之源。但当性能问题真正出现时,优化就是救命稻草。” —— Donald Knuth 在前三篇文章中,我们学习了索引、事务、锁的原理。但光有理论还不够,如何定位和优化慢查询? 想象这样的场景: 凌晨3点,你被一通电话吵醒: "数据库快挂了,所有查询都超时!" 你打开监控,发现: - CPU 100% - 慢查询日志爆满 - 某个SQL执行了10秒还没返回 如何快速定位问题?如何优化这个慢查询? 这就是查询优化的核心价值:让慢查询变快,让系统起死回生。 今天,我们从第一性原理出发,深度剖析MySQL的查询优化: SQL执行流程: 客户端 → 连接器 → 解析器 → 优化器 → 执行器 → 存储引擎 ↓ ↓ ↓ ↓ ↓ 权限检查 语法解析 生成计划 执行查询 返回数据 性能优化: 慢查询 → EXPLAIN → 找到瓶颈 → 优化索引 → 改写SQL → 性能飞跃 10秒 分析 全表扫描 建索引 覆盖索引 10ms 我们还将通过10个真实案例,将慢查询从10秒优化到10ms,性能提升1000倍。 一、SQL执行流程:从SQL到结果集 理解查询优化,首先要理解SQL是如何执行的。 1.1 MySQL的架构:两层结构 ┌─────────────────────────────────────────────────────────────┐ │ MySQL Server层 │ ├─────────────────────────────────────────────────────────────┤ │ 连接器 解析器 优化器 执行器 │ │ ↓ ↓ ↓ ↓ │ │ 权限验证 语法解析 生成计划 执行查询 │ └─────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────┐ │ 存储引擎层 │ ├─────────────────────────────────────────────────────────────┤ │ InnoDB MyISAM Memory Archive │ │ ↓ ↓ ↓ ↓ │ │ 事务支持 不支持 内存存储 压缩存储 │ └─────────────────────────────────────────────────────────────┘ Server层与存储引擎的职责分工: ...

2025-11-03 · maneng

数据类型详解:选择合适的数据类型

引言 提出问题 上一篇我们学会了创建表,但在定义列时遇到了很多数据类型: 存储用户ID,用 INT 还是 BIGINT? 存储金额,为什么用 DECIMAL 而不是 FLOAT? 存储用户名,VARCHAR(50) 和 VARCHAR(255) 有什么区别? 存储日期,用 DATE、DATETIME 还是 TIMESTAMP? CHAR 和 VARCHAR 怎么选择? 选择错误的数据类型会导致: 浪费存储空间(INT能搞定的用BIGINT) 性能下降(VARCHAR(255)比VARCHAR(50)慢) 精度丢失(FLOAT存金额会有误差) 查询出错(日期类型选错导致时区问题) 这篇文章将帮你理解MySQL数据类型的底层原理,学会正确选型! 数据类型分类 MySQL数据类型分为五大类: 分类 常用类型 应用场景 数值类型 INT, BIGINT, DECIMAL, FLOAT 年龄、金额、计数器 字符串类型 CHAR, VARCHAR, TEXT 用户名、描述、文章内容 日期时间类型 DATE, TIME, DATETIME, TIMESTAMP 创建时间、生日 二进制类型 BLOB, BINARY 图片、文件(不推荐存数据库) JSON类型 JSON 配置项、扩展字段 数值类型 整数类型 五种整数类型 类型 存储空间 有符号范围 无符号范围 典型应用 TINYINT 1字节 -128 ~ 127 0 ~ 255 年龄、状态码 SMALLINT 2字节 -32,768 ~ 32,767 0 ~ 65,535 分类ID MEDIUMINT 3字节 -838万 ~ 838万 0 ~ 1677万 较少使用 INT 4字节 -21亿 ~ 21亿 0 ~ 42亿 用户ID、商品ID BIGINT 8字节 -922京 ~ 922京 0 ~ 1844京 订单ID、大数据 实战示例 CREATE TABLE user_profile ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID(无符号,0-42亿)', age TINYINT UNSIGNED COMMENT '年龄(0-255)', score SMALLINT DEFAULT 0 COMMENT '积分(-32768~32767)', view_count BIGINT UNSIGNED DEFAULT 0 COMMENT '浏览量(大数据)' ); 有符号 vs 无符号(UNSIGNED) -- 有符号(默认) CREATE TABLE test1 (num INT); -- 范围:-2147483648 ~ 2147483647 -- 无符号(推荐用于ID、计数器) CREATE TABLE test2 (num INT UNSIGNED); -- 范围:0 ~ 4294967295 选择原则: ...

2025-11-20 · maneng

基础增删改查:DML操作入门

引言 提出问题 前面几篇我们学会了创建数据库和表,但表只是一个空壳,现在要学习: 如何往表里插入数据?(INSERT) 如何修改已有的数据?(UPDATE) 如何删除不需要的数据?(DELETE) 如何查询数据?(SELECT) 批量插入和批量更新如何操作? 这就是CRUD操作(Create增、Read读、Update改、Delete删),是使用数据库的核心! DML语句概述 DML(Data Manipulation Language,数据操作语言):操作表中的数据 操作 语句 作用 对应CRUD 插入 INSERT 向表中添加新数据 Create 查询 SELECT 从表中读取数据 Read 更新 UPDATE 修改表中已有数据 Update 删除 DELETE 从表中删除数据 Delete INSERT:插入数据 基础语法 INSERT INTO table_name (column1, column2, ...) VALUES (value1, value2, ...); 实战准备:创建测试表 CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, email VARCHAR(100) NOT NULL, age INT, status TINYINT DEFAULT 1, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); 插入单行数据 -- 方式1:指定列名(推荐) INSERT INTO users (username, email, age) VALUES ('zhangsan', 'zhangsan@example.com', 25); -- 方式2:不指定列名(必须提供所有列的值,按顺序) INSERT INTO users VALUES (NULL, 'lisi', 'lisi@example.com', 30, 1, CURRENT_TIMESTAMP); -- NULL:让AUTO_INCREMENT自动生成ID -- CURRENT_TIMESTAMP:当前时间 推荐使用方式1,因为: ...

2025-11-20 · maneng

MySQL核心原理:关键技术深度解析

引言 通过前5篇文章,我们已经掌握了MySQL的核心技术。本文将深入MySQL内部,理解关键组件的工作原理。 一、InnoDB核心组件 1.1 Buffer Pool:内存缓存池 作用:缓存数据页和索引页,减少磁盘IO Buffer Pool结构: ┌─────────────────────────────────────┐ │ Buffer Pool (默认128MB) │ ├─────────────────────────────────────┤ │ 数据页缓存 (75%) │ │ ├─ Page 1: users表数据 │ │ ├─ Page 2: orders表数据 │ │ └─ ... │ ├─────────────────────────────────────┤ │ 索引页缓存 (20%) │ │ ├─ Index Page 1: uk_username │ │ ├─ Index Page 2: idx_created_time │ │ └─ ... │ ├─────────────────────────────────────┤ │ Undo页缓存 (5%) │ │ └─ 历史版本数据 │ └─────────────────────────────────────┘ 淘汰算法: LRU (Least Recently Used) 热数据: 靠近链表头部 冷数据: 靠近链表尾部 新数据: 先放入中间位置(避免扫描污染) 关键参数: ...

2025-11-03 · maneng

MySQL核心原理:关键技术深度解析

引言 通过前5篇文章,我们已经掌握了MySQL的核心技术。本文将深入MySQL内部,理解关键组件的工作原理。 一、InnoDB核心组件 1.1 Buffer Pool:内存缓存池 作用:缓存数据页和索引页,减少磁盘IO Buffer Pool结构: ┌─────────────────────────────────────┐ │ Buffer Pool (默认128MB) │ ├─────────────────────────────────────┤ │ 数据页缓存 (75%) │ │ ├─ Page 1: users表数据 │ │ ├─ Page 2: orders表数据 │ │ └─ ... │ ├─────────────────────────────────────┤ │ 索引页缓存 (20%) │ │ ├─ Index Page 1: uk_username │ │ ├─ Index Page 2: idx_created_time │ │ └─ ... │ ├─────────────────────────────────────┤ │ Undo页缓存 (5%) │ │ └─ 历史版本数据 │ └─────────────────────────────────────┘ 淘汰算法: LRU (Least Recently Used) 热数据: 靠近链表头部 冷数据: 靠近链表尾部 新数据: 先放入中间位置(避免扫描污染) 关键参数: ...

2025-11-03 · maneng

约束与完整性:主键、外键、唯一、非空

引言 提出问题 前面我们学会了增删改查,但遇到一些问题: 如何防止插入重复的用户名? 如何保证用户年龄不能为负数? 如何保证订单必须关联到存在的用户? 如何保证邮箱字段不能为空? 这就需要数据库约束(Constraints)来保证数据完整性! 数据完整性的三个层次 1. 实体完整性(Entity Integrity) 定义:每一行数据必须是唯一可识别的 实现方式:主键约束(PRIMARY KEY) CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, -- 主键,唯一标识 username VARCHAR(50) ); 2. 域完整性(Domain Integrity) 定义:列的值必须符合定义的类型和约束 实现方式: 数据类型(INT、VARCHAR等) NOT NULL约束 DEFAULT默认值 CHECK约束(MySQL 8.0.16+) CREATE TABLE users ( id INT PRIMARY KEY, age INT NOT NULL CHECK (age >= 0 AND age <= 150), -- 年龄范围 status TINYINT DEFAULT 1 -- 默认值 ); 3. 参照完整性(Referential Integrity) 定义:表之间的关联必须一致 实现方式:外键约束(FOREIGN KEY) CREATE TABLE orders ( id INT PRIMARY KEY, user_id INT, FOREIGN KEY (user_id) REFERENCES users(id) -- 外键,关联users表 ); 主键约束(PRIMARY KEY) 特点 ✅ 唯一性:值不能重复 ✅ 非空性:值不能为NULL ✅ 每张表只能有一个主键 ✅ 主键可以是单列或多列(复合主键) 单列主键 -- 方式1:列级定义 CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) ); -- 方式2:表级定义 CREATE TABLE users ( id INT AUTO_INCREMENT, username VARCHAR(50), PRIMARY KEY (id) ); 复合主键 -- 学生选课表(学生ID + 课程ID作为主键) CREATE TABLE student_courses ( student_id INT, course_id INT, score INT, PRIMARY KEY (student_id, course_id) -- 复合主键 ); -- 插入数据 INSERT INTO student_courses VALUES (1, 101, 90); -- 成功 INSERT INTO student_courses VALUES (1, 102, 85); -- 成功 INSERT INTO student_courses VALUES (1, 101, 95); -- 失败:主键重复 添加/删除主键 -- 添加主键 ALTER TABLE users ADD PRIMARY KEY (id); -- 删除主键 ALTER TABLE users DROP PRIMARY KEY; -- 修改主键(先删除再添加) ALTER TABLE users DROP PRIMARY KEY; ALTER TABLE users ADD PRIMARY KEY (new_id); 唯一约束(UNIQUE) 特点 ✅ 值不能重复 ✅ 可以为NULL(但只能有一个NULL) ✅ 一张表可以有多个唯一约束 基础用法 CREATE TABLE users ( id INT PRIMARY KEY, username VARCHAR(50) UNIQUE, -- 用户名唯一 email VARCHAR(100) UNIQUE, -- 邮箱唯一 phone VARCHAR(20) ); -- 插入数据 INSERT INTO users VALUES (1, 'zhangsan', 'zhang@example.com', '13800138000'); INSERT INTO users VALUES (2, 'zhangsan', 'li@example.com', '13900139000'); -- ERROR 1062: Duplicate entry 'zhangsan' for key 'username' 复合唯一约束 -- IP访问日志(同一IP在同一天只能记录一次) CREATE TABLE access_logs ( id INT PRIMARY KEY AUTO_INCREMENT, ip VARCHAR(15), access_date DATE, UNIQUE KEY uk_ip_date (ip, access_date) -- 复合唯一约束 ); 添加/删除唯一约束 -- 添加唯一约束 ALTER TABLE users ADD UNIQUE (username); ALTER TABLE users ADD UNIQUE KEY uk_email (email); -- 指定约束名 -- 删除唯一约束 ALTER TABLE users DROP INDEX username; ALTER TABLE users DROP INDEX uk_email; 非空约束(NOT NULL) 特点 ✅ 字段不能为NULL ✅ 必须提供值 基础用法 CREATE TABLE users ( id INT PRIMARY KEY, username VARCHAR(50) NOT NULL, -- 不能为空 email VARCHAR(100) NOT NULL, nickname VARCHAR(50) -- 可以为空 ); -- 插入数据 INSERT INTO users (id, username, email) VALUES (1, 'zhangsan', 'zhang@example.com'); -- 成功 INSERT INTO users (id, username) VALUES (2, 'lisi'); -- 失败:email不能为空 NULL vs 空字符串 -- NULL:未知、不存在 INSERT INTO users (id, username, email, nickname) VALUES (1, 'zhang', 'zhang@example.com', NULL); -- 空字符串:已知但为空 INSERT INTO users (id, username, email, nickname) VALUES (2, 'li', 'li@example.com', ''); -- 查询 SELECT * FROM users WHERE nickname IS NULL; -- 查到id=1 SELECT * FROM users WHERE nickname = ''; -- 查到id=2 重要区别: ...

2025-11-20 · maneng

字符集与校对规则

引言 提出问题 使用MySQL时,经常遇到字符编码问题: 中文乱码:插入"你好"变成"???" emoji存储失败:插入😀报错 utf8和utf8mb4有什么区别? 为什么查询"a"和"A"结果一样? 这就是字符集(Character Set)和校对规则(Collation)的问题! 字符集基础 什么是字符集? 字符集:定义了字符与二进制编码的映射关系。 示例: ASCII:A → 0x41(65) UTF-8:你 → 0xE4BDA0 UTF-8:😀 → 0xF09F9880 MySQL常用字符集 字符集 字节数 支持字符 推荐使用 latin1 1字节 西欧字符 ❌ 不支持中文 gbk 1-2字节 简体中文 ⚠️ 老项目 utf8 1-3字节 多语言 ❌ 不支持emoji utf8mb4 1-4字节 多语言+emoji ✅ 推荐 utf8 vs utf8mb4 ⚠️ 重要:MySQL的utf8不是真正的UTF-8! utf8:最多3字节,不支持emoji(😀需要4字节) utf8mb4:最多4字节,支持emoji和生僻字 -- ❌ 错误:utf8存储emoji CREATE TABLE test (name VARCHAR(50)) CHARACTER SET utf8; INSERT INTO test VALUES ('你好😀'); -- ERROR 1366: Incorrect string value -- ✅ 正确:utf8mb4存储emoji CREATE TABLE test (name VARCHAR(50)) CHARACTER SET utf8mb4; INSERT INTO test VALUES ('你好😀'); -- 成功 查看字符集 -- 查看数据库字符集 SHOW VARIABLES LIKE 'character_set%'; -- 输出示例: +--------------------------+----------------------------+ | Variable_name | Value | +--------------------------+----------------------------+ | character_set_client | utf8mb4 | | character_set_connection | utf8mb4 | | character_set_database | utf8mb4 | | character_set_results | utf8mb4 | | character_set_server | utf8mb4 | | character_set_system | utf8 | +--------------------------+----------------------------+ -- 查看表的字符集 SHOW CREATE TABLE users; 校对规则(Collation) 什么是校对规则? 校对规则:定义字符的比较和排序规则。 ...

2025-11-20 · maneng

数据导入导出

引言 提出问题 在使用MySQL过程中,经常需要: 如何备份数据库? 如何将Excel数据导入MySQL? 如何将MySQL数据导出为Excel或CSV? 如何迁移数据到另一台服务器? 测试环境如何快速导入生产数据? 这就是数据导入导出(Import/Export)的核心技能! mysqldump:数据备份工具 基础语法 mysqldump [options] database [tables] > backup.sql 备份单个数据库 # 备份整个数据库 mysqldump -u root -p mydb > mydb_backup.sql # 执行后,输入密码 Enter password: **** # 查看备份文件 ls -lh mydb_backup.sql # -rw-r--r-- 1 user staff 1.2M Jan 15 10:30 mydb_backup.sql 备份单张表 # 备份指定表 mysqldump -u root -p mydb users > users_backup.sql # 备份多张表 mysqldump -u root -p mydb users orders > tables_backup.sql 备份多个数据库 # 备份多个数据库 mysqldump -u root -p --databases db1 db2 db3 > multi_db_backup.sql 备份所有数据库 # 备份所有数据库(包括系统库) mysqldump -u root -p --all-databases > all_databases_backup.sql 常用选项 # 完整备份命令(推荐) mysqldump -u root -p \ --single-transaction \ # 一致性快照(InnoDB) --routines \ # 包含存储过程和函数 --triggers \ # 包含触发器 --events \ # 包含事件 --default-character-set=utf8mb4 \ # 字符集 mydb > mydb_full_backup.sql 选项 作用 --single-transaction 不锁表,适合InnoDB --lock-tables 锁表备份,适合MyISAM --no-data 只备份结构,不备份数据 --no-create-info 只备份数据,不备份结构 --where='条件' 按条件备份数据 只备份表结构 # 只备份表结构(DDL) mysqldump -u root -p --no-data mydb > mydb_schema.sql 只备份数据 # 只备份数据(不含表结构) mysqldump -u root -p --no-create-info mydb > mydb_data.sql 按条件备份 # 备份最近30天的订单 mysqldump -u root -p mydb orders \ --where="created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY)" \ > orders_recent.sql 数据恢复(导入) 恢复数据库 # 方式1:命令行导入 mysql -u root -p mydb < mydb_backup.sql # 方式2:登录后导入 mysql -u root -p mysql> USE mydb; mysql> SOURCE /path/to/mydb_backup.sql; 创建数据库后恢复 # 如果数据库不存在,先创建 mysql -u root -p -e "CREATE DATABASE mydb CHARACTER SET utf8mb4;" # 再导入数据 mysql -u root -p mydb < mydb_backup.sql 恢复单张表 # 恢复单张表 mysql -u root -p mydb < users_backup.sql 进度显示 # 使用pv工具显示进度 pv mydb_backup.sql | mysql -u root -p mydb # 输出示例: # 1.2GB 0:05:30 [3.6MB/s] [=========> ] 45% ETA 0:06:45 CSV/TXT文件导入导出 导出为CSV -- 导出为CSV文件 SELECT * FROM users INTO OUTFILE '/var/lib/mysql-files/users.csv' FIELDS TERMINATED BY ',' ENCLOSED BY '"' LINES TERMINATED BY '\n'; -- 执行结果: Query OK, 1000 rows affected (0.02 sec) 注意事项: ...

2025-11-20 · maneng

第一个完整案例:用户管理系统

引言 回顾与展望 前9篇文章中,我们学习了: ✅ 数据库基础概念 ✅ MySQL安装配置 ✅ DDL(创建表) ✅ 数据类型选择 ✅ DML(增删改查) ✅ 约束与完整性 ✅ 字符集处理 ✅ 数据导入导出 现在,让我们综合运用这些知识,搭建一个完整的用户管理系统! 系统需求 实现一个用户管理系统,支持: 用户注册(唯一用户名、邮箱验证) 用户登录(密码加密存储) 个人信息管理(修改资料、上传头像) 角色权限管理(管理员、普通用户) 登录日志记录 数据统计(用户数、登录次数) 需求分析 功能模块 用户管理系统 ├── 用户模块 │ ├── 注册 │ ├── 登录 │ ├── 修改资料 │ └── 注销账户 ├── 角色权限模块 │ ├── 角色管理 │ ├── 权限分配 │ └── 权限验证 └── 日志模块 ├── 登录日志 └── 操作日志 数据库设计 需要以下数据表: users:用户表(核心表) roles:角色表 permissions:权限表 role_permissions:角色权限关联表 user_roles:用户角色关联表 login_logs:登录日志表 数据库设计 创建数据库 -- 创建数据库 CREATE DATABASE IF NOT EXISTS user_management CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 使用数据库 USE user_management; 1. 用户表(users) CREATE TABLE users ( id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID', username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名(唯一)', password VARCHAR(255) NOT NULL COMMENT '密码(SHA256加密)', email VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱(唯一)', phone VARCHAR(20) COMMENT '手机号', real_name VARCHAR(50) COMMENT '真实姓名', nickname VARCHAR(50) COMMENT '昵称', avatar VARCHAR(255) COMMENT '头像URL', gender ENUM('male', 'female', 'other') DEFAULT 'other' COMMENT '性别', birthday DATE COMMENT '生日', bio TEXT COMMENT '个人简介', status TINYINT DEFAULT 1 COMMENT '状态:1正常 0禁用 2待激活', login_count INT UNSIGNED DEFAULT 0 COMMENT '登录次数', last_login_at TIMESTAMP NULL COMMENT '最后登录时间', last_login_ip VARCHAR(45) COMMENT '最后登录IP', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', deleted_at TIMESTAMP NULL COMMENT '删除时间(软删除)', INDEX idx_username (username), INDEX idx_email (email), INDEX idx_phone (phone), INDEX idx_status (status), INDEX idx_created_at (created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; 2. 角色表(roles) CREATE TABLE roles ( id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL UNIQUE COMMENT '角色名称', display_name VARCHAR(100) COMMENT '显示名称', description VARCHAR(255) COMMENT '角色描述', level INT DEFAULT 0 COMMENT '角色级别(数字越大权限越高)', status TINYINT DEFAULT 1 COMMENT '状态:1启用 0禁用', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_name (name), INDEX idx_level (level) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表'; 3. 权限表(permissions) CREATE TABLE permissions ( id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL UNIQUE COMMENT '权限名称(如user.create)', display_name VARCHAR(100) COMMENT '显示名称', description VARCHAR(255) COMMENT '权限描述', module VARCHAR(50) COMMENT '所属模块(user、order、product)', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_name (name), INDEX idx_module (module) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限表'; 4. 角色权限关联表(role_permissions) CREATE TABLE role_permissions ( id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, role_id INT UNSIGNED NOT NULL COMMENT '角色ID', permission_id INT UNSIGNED NOT NULL COMMENT '权限ID', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_role_permission (role_id, permission_id), INDEX idx_role_id (role_id), INDEX idx_permission_id (permission_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色权限关联表'; 5. 用户角色关联表(user_roles) CREATE TABLE user_roles ( id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, user_id INT UNSIGNED NOT NULL COMMENT '用户ID', role_id INT UNSIGNED NOT NULL COMMENT '角色ID', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_user_role (user_id, role_id), INDEX idx_user_id (user_id), INDEX idx_role_id (role_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表'; 6. 登录日志表(login_logs) CREATE TABLE login_logs ( id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, user_id INT UNSIGNED NOT NULL COMMENT '用户ID', login_ip VARCHAR(45) NOT NULL COMMENT '登录IP', user_agent TEXT COMMENT '浏览器UA', status TINYINT DEFAULT 1 COMMENT '状态:1成功 0失败', failure_reason VARCHAR(255) COMMENT '失败原因', login_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '登录时间', INDEX idx_user_id (user_id), INDEX idx_login_at (login_at), INDEX idx_status (status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='登录日志表'; 初始化数据 插入默认角色 -- 插入角色 INSERT INTO roles (name, display_name, description, level) VALUES ('admin', '超级管理员', '拥有所有权限', 100), ('manager', '管理员', '拥有大部分管理权限', 50), ('user', '普通用户', '基础用户权限', 1); 插入权限 -- 插入权限 INSERT INTO permissions (name, display_name, description, module) VALUES -- 用户模块 ('user.create', '创建用户', '可以创建新用户', 'user'), ('user.read', '查看用户', '可以查看用户信息', 'user'), ('user.update', '修改用户', '可以修改用户信息', 'user'), ('user.delete', '删除用户', '可以删除用户', 'user'), -- 角色模块 ('role.create', '创建角色', '可以创建新角色', 'role'), ('role.read', '查看角色', '可以查看角色', 'role'), ('role.update', '修改角色', '可以修改角色', 'role'), ('role.delete', '删除角色', '可以删除角色', 'role'), -- 系统模块 ('system.setting', '系统设置', '可以修改系统设置', 'system'), ('system.log', '查看日志', '可以查看系统日志', 'system'); 分配权限给角色 -- 超级管理员拥有所有权限 INSERT INTO role_permissions (role_id, permission_id) SELECT 1, id FROM permissions; -- 管理员拥有用户模块权限 INSERT INTO role_permissions (role_id, permission_id) SELECT 2, id FROM permissions WHERE module = 'user'; -- 普通用户只有查看权限 INSERT INTO role_permissions (role_id, permission_id) SELECT 3, id FROM permissions WHERE name IN ('user.read'); 创建管理员账户 -- 创建超级管理员 INSERT INTO users (username, password, email, real_name, status) VALUES ( 'admin', SHA2('Admin@123456', 256), -- 密码加密 'admin@example.com', '系统管理员', 1 ); -- 分配超级管理员角色 INSERT INTO user_roles (user_id, role_id) VALUES (1, 1); -- user_id=1, role_id=1(超级管理员) 核心功能实现 功能1:用户注册 -- 注册新用户 INSERT INTO users (username, password, email, phone, real_name) VALUES ( 'zhangsan', SHA2('Password@123', 256), -- 密码加密 'zhangsan@example.com', '13800138000', '张三' ); -- 获取刚插入的用户ID SET @new_user_id = LAST_INSERT_ID(); -- 分配默认角色(普通用户) INSERT INTO user_roles (user_id, role_id) VALUES (@new_user_id, 3); -- role_id=3(普通用户) 功能2:用户登录 -- 验证用户名和密码 SELECT u.id, u.username, u.email, u.real_name, u.nickname, u.avatar, u.status, GROUP_CONCAT(r.name) AS roles FROM users u LEFT JOIN user_roles ur ON u.id = ur.user_id LEFT JOIN roles r ON ur.role_id = r.id WHERE u.username = 'zhangsan' AND u.password = SHA2('Password@123', 256) AND u.status = 1 GROUP BY u.id; -- 如果查到记录,登录成功 -- 更新登录信息 UPDATE users SET login_count = login_count + 1, last_login_at = CURRENT_TIMESTAMP, last_login_ip = '192.168.1.100' WHERE id = 2; -- 记录登录日志 INSERT INTO login_logs (user_id, login_ip, user_agent, status) VALUES (2, '192.168.1.100', 'Mozilla/5.0...', 1); 功能3:权限验证 -- 检查用户是否拥有指定权限 SELECT COUNT(*) AS has_permission FROM users u JOIN user_roles ur ON u.id = ur.user_id JOIN role_permissions rp ON ur.role_id = rp.role_id JOIN permissions p ON rp.permission_id = p.id WHERE u.id = 2 AND p.name = 'user.create'; -- 如果结果>0,说明有权限 功能4:修改个人信息 -- 修改昵称和头像 UPDATE users SET nickname = '张小三', avatar = 'https://example.com/avatars/zhangsan.jpg', updated_at = CURRENT_TIMESTAMP WHERE id = 2; 功能5:修改密码 -- 验证旧密码,修改新密码 UPDATE users SET password = SHA2('NewPassword@456', 256), updated_at = CURRENT_TIMESTAMP WHERE id = 2 AND password = SHA2('Password@123', 256); -- 验证旧密码 -- 检查affected rows -- 如果=1,说明旧密码正确,修改成功 -- 如果=0,说明旧密码错误 功能6:软删除用户 -- 软删除(推荐) UPDATE users SET status = 0, deleted_at = CURRENT_TIMESTAMP WHERE id = 2; -- 查询时排除已删除用户 SELECT * FROM users WHERE deleted_at IS NULL -- 未删除 AND status = 1; -- 正常状态 功能7:数据统计 -- 统计总用户数 SELECT COUNT(*) AS total_users FROM users WHERE deleted_at IS NULL; -- 统计各角色用户数 SELECT r.display_name AS role_name, COUNT(ur.user_id) AS user_count FROM roles r LEFT JOIN user_roles ur ON r.id = ur.role_id LEFT JOIN users u ON ur.user_id = u.id AND u.deleted_at IS NULL GROUP BY r.id, r.display_name ORDER BY user_count DESC; -- 统计最近7天注册用户 SELECT DATE(created_at) AS date, COUNT(*) AS register_count FROM users WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY) GROUP BY DATE(created_at) ORDER BY date; -- 统计最近7天登录日志 SELECT DATE(login_at) AS date, COUNT(*) AS login_count, COUNT(DISTINCT user_id) AS unique_users FROM login_logs WHERE login_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY) AND status = 1 GROUP BY DATE(login_at) ORDER BY date; 数据完整性验证 测试1:唯一约束 -- 尝试注册重复用户名 INSERT INTO users (username, password, email) VALUES ('zhangsan', SHA2('test', 256), 'zhangsan2@example.com'); -- ERROR 1062: Duplicate entry 'zhangsan' for key 'username' -- 尝试注册重复邮箱 INSERT INTO users (username, password, email) VALUES ('zhangsan2', SHA2('test', 256), 'zhangsan@example.com'); -- ERROR 1062: Duplicate entry 'zhangsan@example.com' for key 'email' 测试2:非空约束 -- 尝试注册时不提供密码 INSERT INTO users (username, email) VALUES ('test', 'test@example.com'); -- ERROR 1364: Field 'password' doesn't have a default value 测试3:数据一致性 -- 查询用户的角色和权限 SELECT u.username, r.display_name AS role, GROUP_CONCAT(p.display_name) AS permissions FROM users u JOIN user_roles ur ON u.id = ur.user_id JOIN roles r ON ur.role_id = r.id LEFT JOIN role_permissions rp ON r.id = rp.role_id LEFT JOIN permissions p ON rp.permission_id = p.id WHERE u.id = 2 GROUP BY u.id, u.username, r.display_name; 性能优化 索引优化 -- 查看表的索引 SHOW INDEX FROM users; -- 为常用查询字段添加索引 CREATE INDEX idx_last_login_at ON users(last_login_at); CREATE INDEX idx_login_count ON users(login_count); -- 复合索引(用户名+状态) CREATE INDEX idx_username_status ON users(username, status); 查询优化 -- ❌ 不推荐:SELECT * SELECT * FROM users WHERE username = 'zhangsan'; -- ✅ 推荐:只查询需要的列 SELECT id, username, email, real_name FROM users WHERE username = 'zhangsan'; -- ✅ 推荐:使用LIMIT限制结果 SELECT * FROM users ORDER BY created_at DESC LIMIT 10; 安全加固 1. 密码安全 -- ✅ 使用SHA256加密(基础) SHA2('password', 256) -- ✅ 更安全:加盐(Salt) CONCAT(SHA2(CONCAT('password', 'random_salt'), 256)) -- ✅ 最佳实践:使用bcrypt(应用层实现) 2. SQL注入防护 -- ❌ 危险:字符串拼接 SELECT * FROM users WHERE username = '{$username}'; -- 如果username = "admin' OR '1'='1",会查出所有用户 -- ✅ 安全:使用预处理语句(Prepared Statement) -- 在应用层使用PDO或MySQLi的预处理 3. 权限最小化 -- 创建只读用户(用于查询) CREATE USER 'readonly'@'localhost' IDENTIFIED BY 'password'; GRANT SELECT ON user_management.* TO 'readonly'@'localhost'; -- 创建普通用户(不能删除) CREATE USER 'webapp'@'localhost' IDENTIFIED BY 'password'; GRANT SELECT, INSERT, UPDATE ON user_management.* TO 'webapp'@'localhost'; 完整测试流程 1. 创建测试用户 -- 注册3个测试用户 INSERT INTO users (username, password, email, real_name) VALUES ('alice', SHA2('Alice@123', 256), 'alice@example.com', 'Alice'), ('bob', SHA2('Bob@123', 256), 'bob@example.com', 'Bob'), ('charlie', SHA2('Charlie@123', 256), 'charlie@example.com', 'Charlie'); -- 分配角色 INSERT INTO user_roles (user_id, role_id) VALUES (LAST_INSERT_ID()-2, 2), -- alice: manager (LAST_INSERT_ID()-1, 3), -- bob: user (LAST_INSERT_ID(), 3); -- charlie: user 2. 测试登录 -- Alice登录 INSERT INTO login_logs (user_id, login_ip, status) VALUES (2, '192.168.1.101', 1); -- Bob登录 INSERT INTO login_logs (user_id, login_ip, status) VALUES (3, '192.168.1.102', 1); -- 登录失败(密码错误) INSERT INTO login_logs (user_id, login_ip, status, failure_reason) VALUES (4, '192.168.1.103', 0, '密码错误'); 3. 测试权限 -- Alice(管理员)创建用户 SELECT '权限验证:Alice创建用户' AS action; SELECT COUNT(*) AS has_permission FROM users u JOIN user_roles ur ON u.id = ur.user_id JOIN role_permissions rp ON ur.role_id = rp.role_id JOIN permissions p ON rp.permission_id = p.id WHERE u.username = 'alice' AND p.name = 'user.create'; -- 结果:1(有权限) -- Bob(普通用户)创建用户 SELECT '权限验证:Bob创建用户' AS action; SELECT COUNT(*) AS has_permission FROM users u JOIN user_roles ur ON u.id = ur.user_id JOIN role_permissions rp ON ur.role_id = rp.role_id JOIN permissions p ON rp.permission_id = p.id WHERE u.username = 'bob' AND p.name = 'user.create'; -- 结果:0(无权限) 总结与展望 本案例涵盖的知识点 ✅ 数据库设计: ...

2025-11-20 · maneng

单表查询进阶:WHERE条件与运算符

引言 在第一阶段我们学习了基础的 SELECT 查询,但在实际开发中,简单的 SELECT * FROM table 远远不够。我们需要根据各种复杂的业务条件来过滤数据,比如: 查询价格在100-500元之间的商品 查找姓"张"的所有员工 筛选订单金额大于1000且状态为"已支付"的订单 查询手机号为空或邮箱未验证的用户 这些都需要通过 WHERE 子句配合各种运算符来实现。 为什么WHERE查询如此重要? 数据过滤的核心:90%的查询都需要条件过滤 性能的关键:合理的WHERE条件能利用索引,大幅提升查询速度 业务逻辑的体现:复杂的业务规则需要通过条件组合来实现 数据安全:通过WHERE条件控制数据访问范围 本文将系统讲解WHERE子句的各类运算符和使用技巧,让你能够编写出精准、高效的条件查询。 一、WHERE子句基础 1.1 WHERE的基本语法 SELECT column1, column2, ... FROM table_name WHERE condition; 执行顺序: FROM:确定要查询的表 WHERE:过滤出符合条件的行 SELECT:选择要返回的列 1.2 准备测试数据 让我们创建一个商品表来演示各种查询: -- 创建商品表 CREATE TABLE products ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, category VARCHAR(50), price DECIMAL(10, 2), stock INT, created_at DATE, description TEXT ); -- 插入测试数据 INSERT INTO products (name, category, price, stock, created_at, description) VALUES ('iPhone 15 Pro', '手机', 7999.00, 50, '2024-09-20', 'Apple最新旗舰手机'), ('华为Mate 60', '手机', 6999.00, 80, '2024-08-15', '华为高端旗舰'), ('小米14', '手机', 3999.00, 120, '2024-10-01', '小米年度旗舰'), ('MacBook Pro', '电脑', 14999.00, 30, '2024-07-10', 'Apple笔记本电脑'), ('联想ThinkPad', '电脑', 8999.00, 45, '2024-06-20', '商务笔记本'), ('AirPods Pro', '耳机', 1999.00, 200, '2024-09-01', '苹果降噪耳机'), ('索尼WH-1000XM5', '耳机', 2499.00, 60, '2024-05-15', '索尼降噪耳机'), ('iPad Air', '平板', 4799.00, 70, '2024-08-20', 'Apple平板电脑'), ('小米平板6', '平板', 1999.00, 90, '2024-07-25', '小米高性价比平板'), ('罗技MX Master', '鼠标', 699.00, 150, '2024-04-10', '罗技旗舰鼠标'); 二、比较运算符 比较运算符用于比较两个值的大小关系。 ...

2025-11-20 · maneng

如约数科科技工作室

浙ICP备2025203501号

👀 本站总访问量 ...| 👤 访客数 ...| 📅 今日访问 ...