MySQL (三):库操作、表操作、性能分析
一、数据库操作基础
1.1 查看现有数据库
在 MySQL 中,可以使用SHOW DATABASES;
命令查看当前服务器上所有的数据库。这个命令会返回一个包含所有数据库名称的列表,方便用户了解服务器上的数据库资源。
SHOW DATABASES;
1.2 创建新数据库
使用CREATE DATABASE
语句可以创建一个新的数据库。例如,创建一个名为chat
的数据库:
CREATE DATABASE chat;
CREATE DATABASE chat;看似简单,但背后涉及字符集和排序规则的默认配置。实际开发中,建议显式指定字符集以避免乱码问题:
CREATE DATABASE chat CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
- utf8mb4:支持包括 emoji 在内的所有 Unicode 字符(传统 utf8 仅支持 3 字节字符)
- utf8mb4_unicode_ci:按 Unicode 标准排序,支持多语言正确比较
查看数据库详细信息的命令:
SHOW CREATE DATABASE chat; -- 查看创建语句及字符集配置
1.3 使用数据库
在操作特定数据库之前,需要先使用USE
语句切换到该数据库:
USE chat;
1.4 删除数据库
删除数据库需要谨慎操作,因为这会永久删除数据库中的所有数据。使用DROP DATABASE
语句:
DROP DATABASE chat;
DROP DATABASE是高危操作,生产环境中应:
- 执行前先备份(mysqldump -u root -p chat > chat_backup.sql)
- 启用数据库审计日志(如开启 MySQL 的 general log 记录操作)
- 限制 DROP 权限(通过 GRANT 语句控制用户权限)
二、数据表操作详解
2.1 创建数据表
创建数据表时需要定义表结构,包括字段名、数据类型和约束条件。以下是一个创建user
表的示例:
CREATE TABLE user (id INT UNSIGNED PRIMARY KEY NOT NULL AUTO_INCREMENT COMMENT '用户唯一标识',name VARCHAR(50) UNIQUE NOT NULL COMMENT '用户名,最长50字符',age TINYINT UNSIGNED NOT NULL COMMENT '年龄,0-255范围',sex ENUM('w','m') NOT NULL DEFAULT 'm' COMMENT '性别:w-女,m-男'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户基本信息表';
这个表包含四个字段:
id
:自增主键,确保唯一性name
:用户名,唯一且不能为空age
:年龄,使用 TINYINT 节省空间sex
:性别,使用 ENUM 类型限制取值范围
- AUTO_INCREMENT 的隐患:自增主键达到最大值(INT UNSIGNED 为 4294967295)后,新插入会报Duplicate entry错误,需提前规划字段类型(如改用 BIGINT)
- ENUM 类型的取舍:适合值固定且数量少的场景(如性别),但修改枚举值需 ALTER TABLE,不如 tinyint 灵活(可关联字典表)
2.2 查看表结构
使用DESC
命令可以查看表的详细结构:
DESC user;
DESC user;只能看到基础结构,更全面的信息来自:
SHOW FULL COLUMNS FROM user; -- 显示字段注释、字符集等完整信息
SHOW TABLE STATUS LIKE 'user'; -- 查看表引擎、数据量、碎片率等
2.3 删除数据表
删除表会删除表中的所有数据和表结构:
DROP TABLE user;
2.4 查看表创建语句
使用SHOW CREATE TABLE
可以查看创建表的原始 SQL 语句:
SHOW CREATE TABLE user;
三、数据增删改查(CRUD)
3.1 插入数据
单条插入
INSERT INTO user(name, age, sex) VALUES('zhangsan', 20, 'M');
批量插入
INSERT INTO user(name, age, sex) VALUES('zhangsan', 20, 'M'), ('lisi', 22, 'M');
两种插入方式的性能差异
从网络通信角度分析,这两种插入方式存在显著差异:
单条插入与批量插入的性能差异,本质是TCP 连接开销的影响:
- 单条插入:N 条数据需要 N 次 TCP 三次握手(3N 个数据包)+ N 次四次挥手(4N 个数据包)
- 批量插入:1 次 TCP 连接即可完成,仅 3+4=7 个数据包
批量插入的最佳实践:
- 单次插入不超过 1000 行(避免数据包过大导致超时)
- 结合事务:START TRANSACTION; ... COMMIT;减少日志刷新次数
- 使用LOAD DATA LOCAL INFILE导入超大量数据(比 INSERT 快 10 倍以上)
在大数据量插入场景下,批量插入的性能优势明显。
3.2 删除数据
删除指定条件的数据:
DELETE FROM user WHERE id=1;
注意事项:
DELETE FROM user WHERE id=1;执行后:
- 自增主键不会回退(AUTO_INCREMENT值保持当前最大值)
- InnoDB 会标记数据为删除(墓碑标记),不立即释放空间(产生碎片)
- 关联表的外键约束可能触发级联删除(需提前了解表关系)
清理碎片的方法:
OPTIMIZE TABLE user;
-- 适用于MyISAM,InnoDB推荐ALTER TABLE user ENGINE=InnoDB;
3.3 更新数据
更新数据可以修改表中的现有记录:
UPDATE user SET age=age+1 WHERE name='zh
性能取决于:
- name字段是否有索引:无索引则全表扫描,有索引则快速定位
- 更新字段是否为索引列:修改索引列会导致索引重建,开销更大
- 事务隔离级别:高隔离级别(如 REPEATABLE READ)可能产生更多锁等待
四、单表查询
4.1 基础查询
查询所有字段:
SELECT * FROM user;
查询指定字段:
SELECT id, name, age FROM user;
4.2 条件查询
使用WHERE
子句进行条件筛选:
SELECT id, name, age FROM user WHERE age >= 20 AND age <= 22;
使用BETWEEN
简化范围查询:
SELECT id, name, age FROM user WHERE age BETWEEN 20 AND 22;
使用IN
和NOT IN
进行枚举值查询:
SELECT id, name, age FROM user WHERE age IN (20, 21);
SELECT id, name, age FROM user WHERE age NOT IN (20, 21);
以WHERE age BETWEEN 20 AND 22为例,MySQL 的执行步骤:
1. 检查age是否有索引:
- 有索引:通过 B + 树快速定位范围数据
- 无索引:全表扫描(逐行判断条件)
2. 过滤出符合条件的记录(server 层操作)
3. 提取需要的字段返回
范围查询的注意事项:
- BETWEEN包含边界值,与>= AND <=完全等价
- IN (20,21)在值数量少时高效,超过 5 个建议用BETWEEN或子查询
4.3 模糊查询
使用LIKE
进行模糊匹配:
SELECT id FROM user WHERE name LIKE 'zhang%';
%
匹配任意个字符,_
匹配单个字符。
LIKE 'zhang%'与LIKE '%zhang'的本质区别:
- zhang%:可利用name字段的前缀索引(前提name索引有效)
- %zhang:无法使用索引(必须全表扫描)
优化模糊查询的方案:
- 前缀匹配优先('zhang%')
- 数据量小时可用LOCATE('zhang', name) > 0替代%zhang%
- 大数据量场景引入全文索引(FULLTEXT INDEX)或搜索引擎(如 Elasticsearch)
4.4 去重查询
使用DISTINCT
去除重复值:
SELECT DISTINCT age FROM user;
4.5 结果集合并
使用UNION
和UNION ALL
合并多个查询结果:
SELECT id, name, age FROM user WHERE age >= 20 UNION ALL SELECT id FROM user WHERE name='zhangsan';
4.6 回表
回表是指在索引中找到记录的主键后,还需要回到主键索引中获取完整数据的过程。例如:
SELECT * FROM user WHERE name='zhangsan';
如果name
字段有索引,查询会先通过索引找到主键,再通过主键获取其他字段的值,这就是回表。
当执行SELECT * FROM user WHERE name='zhangsan';且name有普通索引时:
- 先在name索引树找到对应的主键 id(索引扫描)
- 再到主键索引树查询完整记录(回表操作)
避免回表的方法:使用覆盖索引
-- 创建包含所需字段的联合索引
CREATE INDEX idx_name ON user(name, age, sex);
-- 此时查询无需回表
SELECT name, age, sex FROM user WHERE name='zhangsan';
五、分页查询
5.1 基本分页
使用LIMIT
实现分页:
SELECT * FROM user LIMIT 3; -- 取前三条记录
SELECT * FROM user LIMIT 1, 3; -- 从第2条记录开始取3条
SELECT * FROM user LIMIT 3 OFFSET 1; -- 同上
当偏移量超过 10 万时,LIMIT 100000, 20的执行逻辑是:
- 扫描前 100020 条记录
- 丢弃前 100000 条,返回最后 20 条
这就是为什么随着页码增大,分页查询会越来越慢。
5.2 分页性能优化
当偏移量很大时,传统分页方式性能较差。例如:
SELECT * FROM user LIMIT 10000, 20;
这种查询会扫描前 10000 条记录,然后丢弃,只返回 20 条,效率极低。
优化方案:使用主键限制替代偏移量
SELECT * FROM user WHERE id > 10000 LIMIT 20;
这种方式直接从指定主键位置开始查询,避免了大量的扫描操作,提高了分页效率。
优势:
- 直接通过主键索引定位,无需扫描前置记录
- 索引查找复杂度为 O (logN),而非全表扫描的 O (N)
局限性:
- 仅适用于连续主键且无删除的场景(有删除会出现跳页)
- 无法支持 "跳转到第 100 页" 的业务需求(除非前端记录上一页最后 id)
5.3 执行计划分析
使用EXPLAIN
命令可以查看 SQL 语句的执行计划:
EXPLAIN SELECT * FROM user LIMIT 3;
执行计划可以帮助我们了解查询是如何执行的,是否使用了索引等,从而进行性能优化。
关注type列:
- ALL:全表扫描(性能差)
- range:范围索引扫描(性能好)
- ref/eq_ref:精确索引匹配(性能最优)
六、排序
6.1 基本排序
使用ORDER BY
进行排序:
SELECT * FROM user ORDER BY name; -- 默认升序
SELECT * FROM user ORDER BY name ASC; -- 显式指定升序
SELECT * FROM user ORDER BY name DESC; -- 降序
SELECT * FROM user ORDER BY name, age; -- 多字段排序
ORDER BY name的执行方式:
- 内存排序:数据量小(小于sort_buffer_size)时,在内存中完成排序
- 外排序:数据量大时,需写入临时文件(磁盘 IO 开销大)
6.2 排序性能优化
优化排序的核心:让排序在索引中完成(避免文件排序)
-- 创建排序字段的索引
CREATE INDEX idx_name ON user(name);
-- 此时排序直接使用索引顺序,无需额外排序
SELECT name FROM user ORDER BY name;
多字段排序的索引设计:
-- 按name升序、age降序排序
CREATE INDEX idx_name_age ON user(name ASC, age DESC);
SELECT name, age FROM user ORDER BY name, age DESC;
七、分组查询
7.1 基本分组
使用GROUP BY
进行分组统计:
SELECT age, COUNT(age) AS number FROM user GROUP BY age;
注意:分组查询中,SELECT
子句中的字段要么是分组字段,要么是聚合函数。否则可能会得到无意义的结果。
7.2 分组条件筛选
使用HAVING
子句对分组结果进行筛选:
SELECT age, COUNT(age) AS number FROM user GROUP BY age HAVING age > 20;
性能优化:优先使用WHERE
条件而不是HAVING
,因为WHERE
可以在分组前过滤数据,而HAVING
是在分组后过滤。如果分组字段有索引,WHERE
还可以利用索引提高性能。
SELECT age, COUNT(age) AS number FROM user WHERE age > 20 GROUP BY age;
7.3 分组与排序
分组查询默认会对结果进行排序,这可能会影响性能。如果不需要排序,可以使用ORDER BY NULL
禁用排序:
SELECT age, COUNT(age) AS number F
GROUP BY age本质是先排序后分组,因此会继承排序的性能问题。执行计划中出现Using temporary; Using filesort表示使用了临时表和文件排序,需优化。
八、存储过程示例
8.1 创建存储过程
以下是一个批量插入数据的存储过程示例:
DELIMITER $
CREATE PROCEDURE add_t_user(IN N INT)
BEGINDECLARE i INT DEFAULT 0;-- 开启事务减少日志刷新START TRANSACTION;WHILE i < N DOINSERT INTO t_user VALUES(NULL, CONCAT(i+1, '@fixbug.com'), i+1);SET i = i + 1;-- 每1000条提交一次,避免事务过大IF i % 1000 = 0 THENCOMMIT;START TRANSACTION;END IF;END WHILE;COMMIT;
END$
DELIMITER ;
8.2 调用存储过程
CALL add_t_user(20000);
存储过程可以将复杂的业务逻辑封装在数据库端,减少客户端与服务器之间的交互次数,提高处理效率
九、性能优化总结
- 批量操作:尽量使用批量插入、更新,减少 TCP 连接开销
- 索引优化:为经常用于查询条件和排序的字段创建索引
- 分页优化:大数据量分页使用主键限制替代偏移量
- 避免回表:查询时尽量只选择需要的字段,避免
SELECT *
- 合理分组:优先使用
WHERE
过滤数据,避免分组后的HAVING
操作 - 禁用不必要排序:分组查询中不需要排序时使用
ORDER BY NULL
通过以上优化方法,可以显著提高 MySQL 数据库的查询和操作性能,提升系统整体效率。