目录
- [1. 引言](#1. 引言)
- [2. 创建数据库与选择数据库](#2. 创建数据库与选择数据库)
- [3. 创建学生信息表](#3. 创建学生信息表)
- [4. 查看表结构信息](#4. 查看表结构信息)
- [5. 向表中插入数据](#5. 向表中插入数据)
- [6. 修改表结构(ALTER TABLE)](#6. 修改表结构(ALTER TABLE))
- [7. 索引(Index)详解](#7. 索引(Index)详解)
- [7.1 索引是什么](#7.1 索引是什么)
- [7.2 索引的优缺点](#7.2 索引的优缺点)
- [7.3 InnoDB 常见索引分类](#7.3 InnoDB 常见索引分类)
- [7.4 基础语法示例](#7.4 基础语法示例)
- [7.5 索引使用示例](#7.5 索引使用示例)
- [7.6 索引使用注意事项](#7.6 索引使用注意事项)
- [8. 总结](#8. 总结)## 1. 引言
在数据库学习和开发中,掌握从基础表操作到高级性能优化(如索引)的完整 SQL 技能至关重要。本文将通过一个连贯的实战案例,手把手演示如何使用 MySQL 完成数据库创建、数据表管理、数据插入、表结构修改,并深入讲解索引的创建、管理与核心原理。我们将创建一个名为 abc 的数据库,并在其中管理一个学生信息表 s1,通过具体的 SQL 语句讲解每个操作步骤及其背后的设计思考。
2. 创建数据库与选择数据库
首先,我们需要创建一个新的数据库来存放我们的数据表。
sql
-- 创建数据库
CREATE DATABASE abc;
执行上述语句后,MySQL 会创建一个名为 abc 的数据库。接下来,我们需要告诉 MySQL 后续的操作都在这个数据库中进行。
sql
-- 使用数据库
USE abc;
USE 语句用于选择当前要操作的数据库。执行后,所有未指定数据库的表操作都将默认在 abc 数据库中进行。
3. 创建学生信息表
在操作表之前,一个好的习惯是确保目标表不存在,避免因重复创建而报错。
sql
-- 删除数据表(如果存在)
DROP TABLE IF EXISTS s1;
DROP TABLE IF EXISTS 是一个安全的做法,它只在表存在时才执行删除操作。
现在,我们来创建核心的学生信息表 s1。
sql
-- 创建学生表
CREATE TABLE s1 (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '编号',
s_name VARCHAR(20) UNIQUE NOT NULL COMMENT '姓名',
age INT NOT NULL COMMENT '年龄',
sex ENUM('男', '女', '未知') DEFAULT '未知' COMMENT '性别',
s_sno CHAR(12) UNIQUE NOT NULL COMMENT '学号',
tel CHAR(11) NOT NULL COMMENT '手机号',
start_time DATE DEFAULT (CURRENT_DATE) COMMENT '入学时间',
status ENUM('在读', '毕业') DEFAULT '在读' COMMENT '状态'
);
表结构说明:
id: 主键,自动增长,作为每条记录的唯一标识。s_name: 学生姓名,VARCHAR(20)类型,UNIQUE约束确保姓名不重复,NOT NULL表示必填。age: 年龄,整型,必填。sex: 性别,使用ENUM类型限定为'男'、'女'、'未知'三种值,默认值为'未知'。s_sno: 学号,定长字符串,唯一且必填。tel: 手机号,定长字符串,必填。start_time: 入学时间,日期类型,默认值为系统当前日期。status: 状态,ENUM类型,表示'在读'或'毕业',默认'在读'。
4. 查看表结构信息
创建表后,我们可以通过多种方式查看其结构。
1. 查看表的基本字段信息(DESC)
sql
-- 查看表结构
DESC s1;
DESC (或 DESCRIBE) 命令会以表格形式列出表的所有字段名、类型、是否为空、键信息、默认值和额外说明。
2. 查看字段信息(SHOW COLUMNS)
sql
-- 查看字段
SHOW COLUMNS FROM s1;
SHOW COLUMNS 与 DESC 功能类似,提供了另一种查看表结构的方式。
3. 查看完整的建表语句
sql
-- 查看创建表语句
SHOW CREATE TABLE s1;
这条命令会返回创建该表的完整 SQL 语句,包括引擎、字符集等所有细节,对于备份或迁移表结构非常有用。
5. 向表中插入数据
表创建好后,我们需要向其中添加数据。以下是插入8条学生记录的示例:
sql
-- 插入数据
INSERT INTO s1 VALUES (NULL, '张三', 18, '男', '2019001', '13888888888', '2020-09-01', '在读');
INSERT INTO s1 VALUES (NULL, '李四', 19, '女', '2019002', '13888888889', '2020-09-01', '在读');
INSERT INTO s1 VALUES (NULL, '王五', 20, '未知', '2019003', '13888888890', '2020-09-01', '在读');
INSERT INTO s1 VALUES (NULL, '赵六', 21, '男', '2019004', '13888888891', '2020-09-01', '在读');
INSERT INTO s1 VALUES (NULL, '孙七', 22, '女', '2019005', '13888888892', '2020-09-01', '在读');
INSERT INTO s1 VALUES (NULL, '周八', 23, '未知', '2019006', '13888888893', '2020-09-01', '在读');
INSERT INTO s1 VALUES (NULL, '吴九', 24, '男', '2019007', '13888888894', '2020-09-01', '在读');
INSERT INTO s1 VALUES (NULL, '郑十', 25, '女', '2019008', '13888888895', '2020-09-01', '在读');
注意:id 字段传入了 NULL,由于其是 AUTO_INCREMENT,数据库会自动为其生成递增值。
6. 修改表结构(ALTER TABLE)
在实际应用中,表结构可能需要调整。ALTER TABLE 语句用于添加、修改或删除表中的列。
1. 添加新字段
假设我们需要为学生表增加一个"身份证号"字段。
sql
-- 添加身份证号字段
ALTER TABLE s1 ADD COLUMN id_card CHAR(18) COMMENT '身份证号';
2. 删除字段
如果添加的字段不再需要,可以将其删除。
sql
-- 删除身份证号字段
ALTER TABLE s1 DROP COLUMN id_card;
7. 索引(Index)详解
7.1 索引是什么
索引是建立在数据表字段上的数据结构(MySQL InnoDB 默认使用 B+Tree),相当于书本的目录。
作用 :快速定位数据,减少全表扫描,极大提升查询(SELECT)语句的速度。
7.2 索引的优缺点
优点:
- 极大加快数据查询速度 :特别是在
WHERE条件筛选、ORDER BY排序、GROUP BY分组时效率提升显著。
缺点:
- 占用额外磁盘存储空间:索引本身需要存储。
- 降低 DML 性能 :进行
INSERT、UPDATE、DELETE操作时,数据库需要额外维护索引结构,可能导致写入变慢。 - 索引不是越多越好:无用的索引只会增加存储与写入开销,需要根据查询需求合理设计。
7.3 InnoDB 常见索引分类
-
主键索引 (PRIMARY)
- 特点:唯一、非空;一张表只能有一个主键。
- InnoDB 特性 :InnoDB 存储引擎中,主键索引即聚簇索引,表数据本身按主键顺序存储在主键索引的叶子节点上。
-
唯一索引 (UNIQUE)
- 特点 :索引列的值不能重复,但允许有一个
NULL值;一张表可以有多个唯一索引。
- 特点 :索引列的值不能重复,但允许有一个
-
普通索引 (INDEX)
- 特点 :最基础的索引类型,仅用于加速查询。允许重复值和
NULL值。
- 特点 :最基础的索引类型,仅用于加速查询。允许重复值和
-
联合索引(复合索引)
- 特点:在多个字段组合上创建的索引。
- 核心原则 :遵循最左前缀原则。查询条件必须包含联合索引的最左列,才能有效利用该索引。
-
全文索引 (FULLTEXT)
- 特点 :专门用于对长文本内容(如
TEXT类型字段)进行全文检索,可以高效替代低效的LIKE '%keyword%'模糊查询。
- 特点 :专门用于对长文本内容(如
7.4 基础语法示例
以下示例基于已添加的 id_card 字段和已有的 s_name、age 字段。
1. 创建普通索引
sql
-- 语法:CREATE INDEX 索引名 ON 表名(字段名);
CREATE INDEX name ON s1(s_name);
2. 创建联合索引
sql
-- 语法:CREATE INDEX 索引名 ON 表名(字段名1, 字段名2);
CREATE INDEX name_age ON s1(s_name, age);
3. 创建全文索引
sql
-- 语法:CREATE FULLTEXT INDEX 索引名 ON 表名(字段名);
CREATE FULLTEXT INDEX name_fulltext ON s1(s_name);
4. 创建唯一索引
sql
-- 语法:CREATE UNIQUE INDEX 索引名 ON 表名(字段名);
CREATE UNIQUE INDEX idcard ON s1(id_card);
5. 查看索引
sql
-- 语法:SHOW INDEX FROM 表名;
SHOW INDEX FROM s1;
该命令会显示表上所有索引的名称、类型、关联的字段、唯一性等信息。
6. 删除索引
sql
-- 语法:DROP INDEX 索引名 ON 表名;
DROP INDEX idcard ON s1;
7.6 索引使用注意事项了解了索引的创建语法后,让我们通过具体的查询示例来展示索引如何提升性能。
示例1:普通索引加速查询
假设我们经常需要按学生姓名查询:
sql
-- 无索引时的查询(全表扫描)
EXPLAIN SELECT * FROM s1 WHERE s_name = '张三';
-- 创建姓名索引后
CREATE INDEX idx_name ON s1(s_name);
-- 再次执行相同查询(使用索引)
EXPLAIN SELECT * FROM s1 WHERE s_name = '张三';
执行计划对比:
- 无索引时:
type为ALL(全表扫描),rows为表总行数 - 有索引时:
type为ref(索引查找),rows为1(精确匹配)
示例2:联合索引与最左前缀原则
创建联合索引后,观察不同查询条件的效果:
sql
-- 创建联合索引
CREATE INDEX idx_name_age ON s1(s_name, age);
-- 情况1:使用索引(包含最左列s_name)
EXPLAIN SELECT * FROM s1 WHERE s_name = '李四' AND age = 19;
-- 情况2:使用索引(只使用最左列s_name)
EXPLAIN SELECT * FROM s1 WHERE s_name = '王五';
-- 情况3:索引失效(未包含最左列s_name)
EXPLAIN SELECT * FROM s1 WHERE age = 20;
-- 情况4:部分使用索引(s_name范围查询,age无法使用索引)
EXPLAIN SELECT * FROM s1 WHERE s_name LIKE '张%' AND age = 18;
示例3:唯一索引防止重复数据
sql
-- 尝试插入重复学号(会失败)
INSERT INTO s1 VALUES (NULL, '测试', 20, '男', '2019001', '13800000000', '2020-09-01', '在读');
-- 错误:Duplicate entry '2019001' for key 's_sno'
-- 查看唯一约束错误信息
SHOW ERRORS;
示例4:索引对排序的优化
sql
-- 无索引时的排序(可能使用文件排序)
EXPLAIN SELECT * FROM s1 ORDER BY s_name;
-- 创建索引后
CREATE INDEX idx_name ON s1(s_name);
-- 再次执行排序(可能使用索引排序)
EXPLAIN SELECT * FROM s1 ORDER BY s_name;
示例5:索引失效场景演示
sql
-- 1. 在索引列上使用函数(索引失效)
EXPLAIN SELECT * FROM s1 WHERE LEFT(s_name, 1) = '张';
-- 2. 隐式类型转换(索引失效)
-- 假设tel字段是CHAR(11),但查询时使用数字
EXPLAIN SELECT * FROM s1 WHERE tel = 13888888888;
-- 3. 使用OR条件(如果OR的某一侧没有索引,可能导致全表扫描)
EXPLAIN SELECT * FROM s1 WHERE s_name = '张三' OR age = 18;
-- 4. 使用不等于(!=或<>)(可能无法使用索引)
EXPLAIN SELECT * FROM s1 WHERE s_name != '张三';
示例6:覆盖索引(Covering Index)
sql
-- 创建覆盖索引
CREATE INDEX idx_covering ON s1(s_name, age, sex);
-- 查询只涉及索引列,无需回表
EXPLAIN SELECT s_name, age FROM s1 WHERE s_name = '赵六' AND age > 20;
-- Extra列显示"Using index"表示使用了覆盖索引
示例7:索引选择性测试
sql
-- 查看字段值的分布情况(评估索引效果)
SELECT
COUNT(DISTINCT sex) as sex_distinct_count,
COUNT(DISTINCT age) as age_distinct_count,
COUNT(DISTINCT s_name) as name_distinct_count,
COUNT(*) as total_rows
FROM s1;
-- 在区分度高的字段上建索引效果更好
-- sex只有3种值,区分度低
-- s_name有8种值(假设不重复),区分度高
示例8:多列索引顺序优化
sql
-- 根据查询频率和选择性决定索引列顺序
-- 假设查询模式1:WHERE age > 20 AND sex = '男'
-- 假设查询模式2:WHERE sex = '女' AND age > 18
-- 创建测试索引
CREATE INDEX idx_age_sex ON s1(age, sex);
CREATE INDEX idx_sex_age ON s1(sex, age);
-- 分别测试两个查询
EXPLAIN SELECT * FROM s1 WHERE age > 20 AND sex = '男';
EXPLAIN SELECT * FROM s1 WHERE sex = '女' AND age > 18;
-- 根据实际查询模式选择更优的索引顺序
示例9:索引维护操作
sql
-- 1. 修改索引名称(MySQL 8.0+)
ALTER TABLE s1 RENAME INDEX idx_name TO idx_student_name;
-- 2. 禁用/启用索引(某些存储引擎支持)
-- ALTER TABLE s1 DISABLE KEYS;
-- ALTER TABLE s1 ENABLE KEYS;
-- 3. 分析索引使用情况
ANALYZE TABLE s1;
-- 4. 检查索引碎片
SHOW TABLE STATUS LIKE 's1';
-- 关注Data_free列,值较大时考虑优化表
OPTIMIZE TABLE s1;
示例10:实际性能对比测试
sql
-- 准备测试数据(插入更多数据以观察性能差异)
DELIMITER $$
CREATE PROCEDURE insert_test_data()
BEGIN
DECLARE i INT DEFAULT 1;
WHILE i <= 10000 DO
INSERT INTO s1 VALUES (
NULL,
CONCAT('测试学生', i),
FLOOR(18 + RAND() * 10),
ELT(FLOOR(1 + RAND() * 3), '男', '女', '未知'),
CONCAT('202400', LPAD(i, 4, '0')),
CONCAT('138', LPAD(FLOOR(RAND() * 100000000), 8, '0')),
DATE_SUB(CURRENT_DATE, INTERVAL FLOOR(RAND() * 365) DAY),
ELT(FLOOR(1 + RAND() * 2), '在读', '毕业')
);
SET i = i + 1;
END WHILE;
END$$
DELIMITER ;
-- 执行存储过程插入测试数据
CALL insert_test_data();
-- 删除无索引时的查询测试
SET profiling = 1;
SELECT * FROM s1 WHERE s_name LIKE '测试学生%' ORDER BY age DESC LIMIT 100;
SHOW PROFILES;
-- 创建索引后的查询测试
CREATE INDEX idx_test_name ON s1(s_name);
SELECT * FROM s1 WHERE s_name LIKE '测试学生%' ORDER BY age DESC LIMIT 100;
SHOW PROFILES;
-- 对比查询时间
SET profiling = 0;
性能优化建议:
- 使用
EXPLAIN分析查询执行计划 - 关注
type列:const>ref>range>index>ALL - 关注
Extra列:避免Using filesort和Using temporary - 定期使用
ANALYZE TABLE更新索引统计信息 - 监控慢查询日志,针对慢查询优化索引
7.5 索引使用注意事项
- 遵循最左前缀原则 :对于联合索引
(A, B, C),查询条件必须包含A,索引才会生效。例如WHERE A=1 AND B=2有效,但WHERE B=2则无法使用该索引。 - 避免在索引列上使用函数或运算 :例如
WHERE YEAR(create_time) = 2023或WHERE amount * 2 > 100会导致索引失效。 - 注意隐式类型转换:例如字符串字段与数字比较时,可能导致索引失效。
- 选择区分度高的字段建立索引:在性别(只有'男'、'女')这种区分度很低的字段上建立索引,效果甚微。应优先在唯一性高或取值分布广泛的字段上建索引。
- 避免过度索引:每个额外的索引都会增加写操作的开销,定期审查并删除未使用的索引。
- 考虑索引大小:过长的索引字段(如TEXT类型)不适合建索引,可考虑前缀索引。
- 注意NULL值的影响:包含NULL值的索引列在查询时可能需要特殊处理。
- 定期维护索引 :使用
OPTIMIZE TABLE或ALTER TABLE ... REBUILD整理索引碎片。## 8. 总结
本文通过一个从零开始的连贯案例,系统演示了 MySQL 数据库操作与性能优化的核心流程:
- 环境准备 :创建数据库 (
CREATE DATABASE) 并切换使用 (USE)。 - 表管理 :安全删除旧表 (
DROP TABLE IF EXISTS)、创建新表 (CREATE TABLE) 并查看其结构 (DESC,SHOW CREATE TABLE)。 - 数据操作 :向表中插入示例数据 (
INSERT INTO)。 - 结构维护 :使用
ALTER TABLE动态添加或删除字段,适应业务变化。 - 性能优化 :深入理解索引的原理、分类与优缺点,并实践了普通索引、联合索引、全文索引、唯一索引的创建 (
CREATE INDEX)、查看 (SHOW INDEX) 和删除 (DROP INDEX),掌握了索引使用的关键注意事项。
掌握这些从基础到进阶的操作,是进行高效数据库设计、开发和优化的坚实基础。建议读者在 MySQL 客户端中亲自执行每一段 SQL 代码,并结合实际业务场景思考索引的设计,以加深理解。