💎 MySQL面试题合集
1. 说说你对 MySQL 锁的理解
1.1 MySQL中的锁分类
- 按粒度分:表锁、行锁和页锁三种
- 按类型分:读锁和写锁(都属于悲观锁)两种
- 按性能分:乐观锁、悲观锁和意向锁
其中,MyISAM和MEMORY存储引擎采用的是表级锁,而InnoDB存储引擎支持行级锁和表级锁。
1.2 表锁、行锁、页锁
表锁
每次操作锁住整张表。开销小,加锁快;发生死锁的概率更小(多事务循环等待情况);锁定粒度大,发生锁冲突的概率最高,并发度最低;一般用在整表数据迁移的场景。
因value字段没有索引,所以这个 SQL 语句会对 locker 表进行加锁,也就是表锁。在事务提交之前,该表数据将无法被其他事务修改。
行锁
对表中一行或多行记录进行加锁控制的方式。开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度最高。在 MySQL 中,行锁是基于索引加载的,即行锁是要加在索引响应的行上。索引失效时会升级为表锁。
value字段添加索引后,这条SQL只会针对value值为2的记录进行加锁,也就是行锁。在事务提交之前,这些行数据将无法被其他事务修改。
页锁
只有BDB存储引擎支持页锁,页锁就是在页的粒度上进行锁定,锁定的数据资源比行锁要多,因为一个页中可以有多个行记录。当我们使用页锁的时候,会出现数据浪费的现象,但这样的浪费最多也就是一个页上的数据行。页锁的开销介于表锁和行锁之间,会出现死锁。锁定粒度介于表锁和行锁之间,并发度一般。
1.3 读锁、写锁、意向锁
读锁(共享锁,S锁)
多个读操作对同一个资源共享同一个锁,多个读操作可以同时进行而不会互相影响。因为读操作并不会改变数据内容,所以多个事务可以共享同一个锁,并行地读取同一个资源,这样可以提高并发效率。
写锁(排它锁,X锁)
当两个事务同时对表中某行数据进行更新操作时,若一个事务先到达并给该行加上排它锁,那么另一个事务就不能在该行加任意类型的锁,直到第一个事务释放了该行的锁。因此,排它锁可以确保在同一时间只有一个事务可以对被加锁的资源进行修改操作,从而避免出现数据竞争和不一致问题。
意向锁(I锁)
数据库中的一种表级锁,在行级锁的基础上引入的一种概念。意向锁是用于解决多粒度锁定而设计的,可以避免行级锁和表级锁之间的冲突。
意向锁分为两种类型:意向共享锁(IS) 和 意向排他锁(IX)。
- 意向共享锁(IS):在一个事务需要对表中某些行加共享锁(S锁)时,事务首先需要获得表的意向共享锁(IS锁)
- 意向排他锁(IX):指的是在一个事务需要对表中某些行加排它锁(X锁)时,事务首先需要获得表的意向排它锁(IX锁)
意向锁简单来说就是添加行锁时,给表添加一个标识表明该表已经存在共享锁或者是排它锁,其他事务需要加锁直接读取该标识判断是否已经存在锁。
1.4 间隙锁(Gap-Lock)
间隙锁就是两个值之间的空隙加锁,是Innodb在可重复读隔离级别下为了解决幻读问题而引入的一种锁机制。需注意间隙锁只会在可重复读隔离级别(REPEATABLE-READ)下才会生效。

此时orders表存在(3,7),(7,10),(10,正无穷)。
操作步骤如下:
- 开启A事务修改id为8的数据,但是不提交事务,同时开启事务B进行插入id为9的数据
- 此时事务B阻塞无法插入成功;再开启事务C进行数据修改,可以修改成功
- 将事务A进行提交,事务B随即插入成功
间隙锁可以锁定一个范围内的所有记录,包括不存在的记录,从而防止其他事务在该范围内插入或修改数据。
其他场景:
- 其他事务无法在这个(7,10)区间插入任何数据
- 其他事务无法在这个(10,正无穷)区间插入任何数据
如果大家在操作时,按以上步骤未能重现,大概率是客户端工具有问题,在单个客户端界面中使用多查询页,有些工具是一个事务,即使是使用 begin 开启事务。可以尝试多开客户端工具重新测试。
1.5 临键锁(Next-key Locks)
临键锁(Next-key Locks)是MySQL InnoDB存储引擎实现的一种数据行级别的锁机制,它是行级锁与间隙锁的组合,即位于索引记录与索引区间之间的一种排它锁。
临键锁主要目的是为了解决幻读问题,能够封锁该条记录相邻两个键之间的空白区域,防止其他事务在这个区域内插入、修改、删除数据。临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。

事务A SQL锁定了[3,7)区间,其他事务无法修改order_id为3的记录,并且无法添加为order_id为4的记录。
2. MySQL 为什么选择B+树作为底层数据结构
MySQL的索引机制中,有一点可谓是路人皆知,既默认使用B+树作为底层的数据结构。有人会说树结构是以二分法查找数据,所以会在很大程度上提升检索性能,这点确实没错,但树结构有那么多,但为什么要选择B+树呢?而不选择二叉树、红黑树或B树呢?下面一起聊一聊这个话题。
2.1 索引为何不选择二叉树?
二叉搜索树是遵守二分搜索法实现的一种数据结构,它具有下面特点:
- 任意节点的左节点不为空时,左节点值小于根节点值
- 右节点不为空时,右节点值大于根节点值
依次存入数据,如果数据是递增的,则原二叉树退化为链表结构。

从动画中可以明显看到,需要经过5次查询才能找到目标数据,由于树结构在磁盘中存储的位置也不连续,所以最终需要发生5次磁盘IO才能找到目标数据。
二叉树不适合作为索引结构的原因:
- 如果索引的字段值是按顺序增长的,二叉树会转变为链表结构,因此检索的过程和全表扫描无异
- 每个节点中只存储一个数据,节点之间还是不连续的,每次磁盘IO只能读取一个数据
2.2 索引为何不选择红黑树?
相比于二叉树,红黑树则进一步做了优化,它是一种自适应的平衡树,会根据插入的节点数量以及节点信息,自动调整树结构来维持平衡。

由于树变矮了,其效果也很明显,在红黑树中只需要经过3次IO就可以找到目标数据,似乎看起来还不错对嘛?但MySQL为啥不用这颗名声远扬的红黑树呢?
红黑树不适合作为索引结构的原因:
- 虽然对比二叉树来说,树高有所降低,但数据量一大时,依旧会有很大的高度
- 每个节点中只存储一个数据,节点之间还是不连续的,每次磁盘IO只能读取一个数据
2.3 索引为何不选择B-Tree?
B树和红黑树相比,其单节点可容纳多个数据,就能在很大程度上改善其性能,使B树的树高远远小于红黑树的高度。

对比红黑树可以发现,每个节点上可以存储更多的数据,且树高固定,数据插入之后横向扩展。观察动画只需要2次IO就可以找到目标数据,搜索效率大大提高了。并且每个节点的元素我们可以自己控制。
那么为什么MySQL没有采用B树结构了?
我们仔细观察可以知道B的叶子节点直接是没有指针的,但是日常查询中包含了大量的范围查找,所以当出现范围查找的时候,会出现多次的IO查找。
B树不适合作为索引结构的原因:
- 虽然对比之前的红黑树更矮,检索数据更快,但对于大范围查询的需求,依旧需要通过多次磁盘IO来检索数据
2.4 索引为何要选择B+Tree?
B+树是在B树的基础进一步优化,一方面节点分为了叶节点和叶子节点两类,另一方面叶子节点之间存在单向链表指针。

B+树相比于B树叶子节点之间多了个单项指针,当需要做范围查询时,只需要定位第一个节点,然后就可以直接根据各节点之间的指针,获取到对应范围之内的所有节点,也就是只需要发生一次IO,就能够确定所查范围之内的所有数据位置。
其实MySQL底层真正的索引结构还对叶子节点之间的指针进行了优化,B+树叶子节点的单向指针无法友好支持的倒叙查询,因此MySQL针对单向指针优化成了双向指针,也就是双向链表结构。即可以快速按正序进行范围查询,而可以快速按倒序进行范围操作,在某些业务场景下又能进一步提升整体性能!
节点分为了叶节点和叶子节点。为什么?
因为B+树的叶节点不存储数据,仅存储指向叶子节点的指针,这样在相同树高时,能存储更多的数据,需要注意的是叶节点数据与叶子结点数据是冗余的。
现在对于MySQL索引为何要选择B+树(变种)的原因大家应该懂了吧。
2.5 可视化工具推荐
数据结构与算法一直以来都让人难以理解,国外有个厉害的程序猿搭建了一个数据结构的动画演示平台,我们在其中能以动画的形式观测数据结构的变化。地址如下:
3. 什么是索引下推
3.1 介绍索引下推
索引下推(INDEX CONDITION PUSHDOWN,简称 ICP)是在 MySQL 5.6 针对扫描二级索引的一项优化改进。总的来说是通过把索引过滤条件下推到存储引擎,来减少 MySQL 存储引擎访问基表的次数以及 MySQL 服务层访问存储引擎的次数。ICP 适用于 MYISAM 和 INNODB,本篇的内容只基于 INNODB。
在讲这个技术之前你得对mysql架构有一个简单的认识,见下图:

- MySQL 服务层:也就是 SERVER 层,用来解析 SQL 的语法、语义、生成查询计划、接管从 MySQL 存储引擎层上推的数据进行二次过滤等等
- MySQL 存储引擎层:按照 MySQL 服务层下发的请求,通过索引或者全表扫描等方式把数据上传到 MySQL 服务层
- MySQL 索引扫描:根据指定索引过滤条件,遍历索引找到索引键对应的主键值后回表过滤剩余过滤条件
- MySQL 索引过滤:通过索引扫描并且基于索引进行二次条件过滤后再回表
3.2 实战数据准备
3.3 索引下推实战
不使用索引下推实现
认真观看数据流转步骤:


使用索引下推实现
认真观看数据流转步骤:


3.4 索引下推的使用条件
- ICP目标是减少全行记录读取,从而减少IO 操作,只能用于非聚簇索引。聚簇索引本身包含的表数据,也就不存在下推一说
- 只能用于range、 ref、 eq_ref、ref_or_null访问方法
- where 条件中是用 and 而非 or 的时候
- ICP适用于分区表
- ICP不支持基于虚拟列上建立的索引,比如说函数索引
- ICP不支持引用子查询作为条件
- ICP不支持存储函数作为条件,因为存储引擎无法调用存储函数
4. 慢SQL你是怎么优化的

4.1 SQL语句优化
4.1.1 准备工作
创建student表:
创建scores表:
添加索引:
插入数据:
4.1.2 SQL优化规则
避免使用select *
阿里规范:

分析:
查看执行计划(select * 走全表扫描,没有用到任何索引,已修正)会走索引,不过需要回表。查询效率偏低;所需要的列都是索引列那么这些列被称为覆盖索引。这种情况下查询的相关字段都能走索引,索引查询的效率相对较高。
通过show warnings语句查看查询列*号替换成表所有字段。

总结:
- 查询时需要先将星号解析成表的所有字段然后在查询,增加查询解析器的成本
- select * 查询一般不走覆盖索引会产生大量的回表查询
- 在实际应用中我们通常只需要使用某几个字段,其他不需要使用的字段也查出来浪费CPU、内存资源
- 文本数据、大字段数据数据传输增加网络消耗
5. 分组统计数据后再进行统计汇总(with rollup)
在MySql中可以使用 with rollup在分组统计数据的基础上再进行数据统计汇总,即将分组后的数据进行汇总。
SELECT ACTORS, SUM(PRICE) FROM MOVIES GROUP BY ACTORS;| ACTORS | SUM(PRICE) |
|---|---|
| 靳东 | 208.60 |
| 刘亦菲 | 196.40 |
| 范冰冰 | 184.40 |
| 成龙 | 246.30 |
| NULL | 65.00 |

SELECT ACTORS, SUM(PRICE) FROM MOVIES GROUP BY ACTORS WITH ROLLUP;| ACTORS | SUM(PRICE) |
|---|---|
| NULL | 65.00 |
| 刘亦菲 | 196.40 |
| 成龙 | 246.30 |
| 范冰冰 | 184.40 |
| 靳东 | 208.60 |
| NULL | 900.70 |

6. 子查询提取(with as)
如果一整句查询中多个子查询都需要使用同一个子查询的结果,那么就可以用with as将共用的子查询提取出来并取一个别名。后面查询语句可以直接用,对于大量复杂的SQL语句起到了很好的优化作用。
需求:获取演员刘亦菲票价大于50且小于65的数据。
WITH ML AS (SELECT * FROM MOVIES WHERE PRICE > 50),
M2 AS (SELECT * FROM MOVIES WHERE PRICE > 65)
SELECT * FROM ML WHERE ML.ID NOT IN (SELECT M2.ID FROM M2) AND ML.ACTORS = '刘亦菲';| ID | MOVIE_NAME | ACTORS | PRICE | RELEASE_DATE |
|---|---|---|---|---|
| 1 | 四大名捕 | 刘亦菲 | 62.50 | 2013-12-21 |

7. 优雅处理数据插入、更新时主键、唯一键重复
在MySql中插入、更新数据有时会遇到主键重复的场景,通常的做法就是先进行删除在插入达到可重复执行的效果,但是这种方法有时候会错误删除数据。
7.1 使用 IGNORE
插入数据时我们可以使用IGNORE,它的作用是插入的值遇到主键或者唯一键重复时自动忽略重复的数据,不影响后面数据的插入,即有则忽略,无则插入。
示例:
SELECT * FROM MOVIES WHERE ID > 13;| ID | MOVIE_NAME | ACTORS | PRICE | RELEASE_DATE |
|---|---|---|---|---|
| 13 | 神话 | 成龙 | 86.50 | 2005-12-22 |
INSERT INTO MOVIES (ID, MOVIE_NAME, ACTORS, PRICE, RELEASE_DATE) VALUES (13,'神话','成龙',100,'2005-12-22');
-- ERROR 1062 (23000): Duplicate entry '13' for key 'MOVIES.PRIMARY'
SELECT ID FROM MOVIES WHERE ID = 13;| ID | MOVIE_NAME | ACTORS | PRICE | RELEASE_DATE |
|---|---|---|---|---|
| 13 | 神话 | 成龙 | 86.50 | 2005-12-22 |
INSERT IGNORE INTO MOVIES (ID, MOVIE_NAME, ACTORS, PRICE, RELEASE_DATE) VALUES (13,'神话','成龙',100,'2005-12-22');
-- Query OK, 0 rows affected, 1 warning (0.00 sec)
INSERT IGNORE INTO MOVIES (ID, MOVIE_NAME, ACTORS, PRICE, RELEASE_DATE) VALUES (14,'神话2','成龙',114,'2005-12-22');
-- Query OK, 1 row affected (0.00 sec)SELECT * FROM MOVIES WHERE ID > 13;| ID | MOVIE_NAME | ACTORS | PRICE | RELEASE_DATE |
|---|---|---|---|---|
| 13 | 神话 | 成龙 | 86.50 | 2005-12-22 |
| 14 | 神话2 | 成龙 | 114.00 | 2005-12-22 |

7.2 使用 REPLACE
还可以使用REPLACE关键字,当插入的记录遇到主键或者唯一键重复时先删除表中重复的记录行再插入,即有则删除+插入,无则插入。
示例:
REPLACE INTO MOVIES (ID, MOVIE_NAME, ACTORS, PRICE, RELEASE_DATE) VALUES (14,'神话2','成龙',100,'2005-12-22');
-- Query OK, 2 rows affected (0.00 sec)
REPLACE INTO MOVIES (ID, MOVIE_NAME, ACTORS, PRICE, RELEASE_DATE) VALUES (15,'神话3','成龙',115,'2005-12-22');
-- Query OK, 1 row affected (0.00 sec)SELECT * FROM MOVIES WHERE ID > 13;| ID | MOVIE_NAME | ACTORS | PRICE | RELEASE_DATE |
|---|---|---|---|---|
| 13 | 神话 | 成龙 | 86.50 | 2005-12-22 |
| 14 | 神话2 | 成龙 | 100.00 | 2005-12-22 |
| 15 | 神话3 | 成龙 | 115.00 | 2005-12-22 |

7.3 使用 ON DUPLICATE KEY UPDATE
更新数据时使用on duplicate key update。它的作用就是当插入的记录遇到主键或者唯一键重复时,会执行后面定义的UPDATE操作。相当于先执行Insert 操作,再根据主键或者唯一键执行update操作,即有就更新,没有就插入。
示例:
INSERT INTO MOVIES (ID, MOVIE_NAME, ACTORS, PRICE, RELEASE_DATE) VALUES
(15,'神话3','成龙',115,'2005-12-22') ON DUPLICATE KEY UPDATE PRICE = PRICE + 10;
-- Query OK, 2 rows affected (0.00 sec)
INSERT INTO MOVIES (ID, MOVIE_NAME, ACTORS, PRICE, RELEASE_DATE) VALUES
(16,'神话4','成龙',75,'2005-12-22') ON DUPLICATE KEY UPDATE PRICE = PRICE + 10;
-- Query OK, 1 row affected (0.00 sec)SELECT * FROM MOVIES WHERE ID > 13;| ID | MOVIE_NAME | ACTORS | PRICE | RELEASE_DATE |
|---|---|---|---|---|
| 13 | 神话 | 成龙 | 86.50 | 2005-12-22 |
| 14 | 神话2 | 成龙 | 100.00 | 2005-12-22 |
| 15 | 神话3 | 成龙 | 125.00 | 2005-12-22 |
| 16 | 神话4 | 成龙 | 75.00 | 2005-12-22 |

8. MySQL表设计经验汇总篇
表设计是每一个后端程序员都无法避开的一块砖,而且这块砖一不小心就很容易烫手,本篇笔记就是为了帮助大家在设计表时能够轻松拿捏。
8.1 命名规范
数据库表名、字段名、索引名等都需要命名规范。命名可读性要高,尽量使用英文,采用驼峰或者下划线分割的方式,让人见名知意。
反例:这些命名过于简单,缺乏描述性,很难让人理解它的含义。
表名:a、b、c
字段名:aaa、bbb、ccc
索引名:index1、index2、index3正例:这些命名就让人见名知意。
表名:customers、orders、products
字段名:customer_id、order_date、product_name
索引名:idx_customer_id、idx_order_dateTips:
- 表名、字段名必须使用小写字母,禁止使用数字开头,禁止使用拼音,并且一般不使用英文缩写
- 主键索引名为pk_字段名;唯一索引名为uk_字段名;普通索引名则为idx_字段名
8.2 选择合适的字段类型
设计表时,需要选择合适字段类型,比如说:
根据数据类型选择字段类型:不同的数据类型应该使用不同的字段类型。
- 整数型数据可以使用 INT 或 BIGINT 类型
- 浮点型数据可以使用 FLOAT 或 DOUBLE 类型
- 字符型数据可以使用 VARCHAR 或 CHAR 类型
考虑数据长度:字段类型应该根据所需存储的数据长度来选择。
- 如果某个字段的数据长度不会超过 10 个字符,则可以使用 CHAR(10) 类型代替 VARCHAR(50) 类型,以节省空间
- 如果存储的值太大,建议字段类型修改为text,同时抽出单独一张表,用主键与之对应
注意精度和小数位数:对于需要精确数值计算的字段(如货币和百分比),应该选择带有精度和小数位数的字段类型(如 DECIMAL)。
考虑数据完整性:字段类型也应该考虑到数据完整性。
- 日期型数据应该使用 DATE 或 DATETIME 类型,以确保输入的日期格式正确
8.3 主键设计要合理
主键的设计在数据库中非常重要,它用于唯一标识表中的每一行数据,并且在数据操作和查询中起到关键作用。通常主键的设计,不要与业务相关联,因为业务是会发生变化的,应当使用自增的 id,并且保持主键的连续性。比如说可以使用优化的雪花 id 等等。
8.4 选择合适的字段长度
首先问大家一个问题,数据库字段长度表示字符长度还是字节长度?
在mysql中,varchar和char类型表示字符长度,而其他类型表示的长度都表示字节长度。
- char(10) 表示字符长度是10
- bigint(4) 表示显示长度是4个字节,但是因为bigint实际长度是8个字节,所以bigint(4)的实际长度就是8个字节
所以在设计表时需要充分考虑一个字段的长度,比如一个用户名字段(它的长度5~20个字符),你觉得应该设置多长呢?可以考虑设置为 varchar(32)。需要注意字段长度一般设置为2的n次方。
8.5 优先考虑逻辑删除,而不是物理删除
什么是物理删除?什么是逻辑删除?
- 物理删除:把数据从硬盘中删除,可释放存储空间
- 逻辑删除:给数据添加一个字段,比如is_deleted,以标记该数据已经逻辑删除
为什么推荐用逻辑删除,不推荐物理删除呢?
- 数据恢复困难
- 物理删除会导致索引树重构
8.6 每个表都需要添加通用字段
- id:主键,一个表必须得有主键,必须
- create_time:创建时间
- creator:创建人
- update_time:修改时间,必须,更新记录时,需要更新它
- update_by:修改人,非必须
- remark:数据记录备注,非必须
8.7 一张表的字段不宜过多
建表的时候一张表的字段不要太多了。尽量不超过 20 个。超出的话优先考虑拆分,也就是通常的查询表,详情表。
- 查询效率:当表中保存的数据数量很大时,查询操作需要检索的数据也会随之增加。如果表的字段数过多,查询操作就需要读取更多的数据,这会导致查询效率变慢
- 存储空间:表的字段数越多,每一行数据占用的存储空间也就越大。对于大型数据库来说,这可能会导致磁盘空间的浪费
- 数据库设计复杂性:当表的字段数过多时,数据库的设计和维护变得更加复杂。这可能涉及到索引和关联表的设计,以确保数据的完整性和一致性
8.8 定义字段尽可能 NOT NULL
如果没有特殊的理由,一般都建议将字段定义为 NOT NULL。为什么呢?
- 首先,NOT NULL 可以防止出现空指针问题
- 其次,NULL值存储也需要额外的空间的,它也会导致比较运算更为复杂,使优化器难以优化SQL
- NULL值有可能会导致索引失效
如果将字段默认设置成一个空字符串或常量值并没有什么不同,且都不会影响到应用逻辑,那就可以将这个字段设置为NOT NULL。
8.9 合理添加索引
当设计表时,需要考虑哪些字段需要加索引,可以遵循以下几个原则:
- 根据查询条件进行选择(高频使用):如果在查询中使用了某个字段作为查询条件,那么这个字段就应该建立索引。例如,在用户表中,如果需要根据用户的姓名进行查询,那么就应该为姓名字段建立索引
- 区分度高的字段优先:如果一个字段的取值范围非常小,例如性别只有男女两种可能,那么这个字段就不适合建立索引。相反,如果一个字段的取值范围很大且区分度高,例如用户ID,那么这个字段就非常适合建立索引
- 不要建立过多的索引:每个表所建立的索引数量应该控制在一个合理的范围内,一般不要超过5个。因为过多的索引会导致写入速度变慢,并占用更多的存储空间
- 联合索引优化:在某些情况下,可以通过联合索引的方式来优化查询速度,减少所需的索引数量。例如,在用户表中,如果需要根据用户姓名和年龄进行查询,那么可以将这两个字段组合成联合索引
假设你有一个订单表,包含订单ID、用户ID、订单金额、订单状态等字段。现在需要根据用户ID和订单状态进行查询,可以考虑为用户ID和订单状态这两个字段建立联合索引。
8.10 不需要严格遵守 3NF,通过业务字段冗余来减少表关联
简单来说就是反范式设计。常见形式是在第三范式(3NF)的基础上,进一步进行冗余,从而减少表关联。
回顾下什么是数据库三范式(3NF)?
- 第一范式:对属性的原子性,要求属性具有原子性,不可再分解
- 第二范式:对记录的唯一性,要求记录有唯一标识,即实体的唯一性,即不存在部分依赖
- 第三范式:对字段的冗余性,要求任何字段不能由其他字段派生出来,它要求字段没有冗余,即不存在传递依赖
假设需要设计一个产品订单表,包含以下字段:订单ID、用户ID、订单日期、产品名称、产品价格、产品数量以及订单总价。正常情况下,可能会分别设计订单表和产品表,并使用外键进行关联。
这个设计方式符合范式要求,但在查询时需要进行表关联操作,可能会降低查询效率。为了提高查询效率,我们可以使用反范式的设计方式,将订单表中的产品名称、产品价格和订单总价冗余存储到订单表中,从而避免关联查询。
通过这种反范式的设计方式,我们可以避免表关联操作,提高查询效率。但同时也带来了一些缺点,例如数据冗余、数据更新困难等。因此,在实际应用中需要根据具体情况进行选择。
8.11 避免使用MySQL保留字
如果库名、表名、字段名等属性含有保留字时,SQL语句必须用反引号来引用属性名称,这将使得SQL语句书写、SHELL脚本中变量的转义等变得非常复杂。
如果你需要使用这些保留字作为表名、列名或其他标识符,你可以考虑以下方法来避免冲突:
- 在标识符前或后添加下划线:例如,将表名命名为 "my_table",列名命名为 "column_name"
- 使用不同的单词或短语:例如,将表名命名为 "orders_table",列名命名为 "order_status"
- 使用反引号(
)将标识符括起来:例如,将表名命名为table,列名命名为column`。请注意,在使用反引号时要小心,确保使用正确的语法和规范
8.12 不搞外键关联,一般都在代码维护
在数据库设计中,使用外键关联是一种良好的实践,可以确保数据的完整性和一致性。外键关联可以帮助维护表之间的关系,防止无效或不一致的数据插入、更新或删除操作。然而,在某些情况下,也存在一些缺点,这可能是导致现在不太推荐使用外键关联的原因之一。
以下是一些这种情况:
- 可能会导致性能问题,尤其是在对大型数据集进行操作时。这是因为每次插入、更新或删除操作都需要进行约束检查,这可能会导致额外的开销和延迟
- 可能会限制数据库的灵活性和可扩展性。例如,如果需要对数据库进行分区或垂直分割,外键关联可能会导致额外的复杂性和限制
- 可能会导致死锁和死循环,特别是在进行并发操作时。这可能会导致数据库出现不稳定的状态,从而影响系统的性能和可用性
- 可能会导致数据库的维护和管理成本的增加。这是因为外键关联需要额外的管理和维护工作,例如添加、修改或删除外键约束时需要额外的测试和验证
因此,在决定是否使用外键关联时,需要考虑实际业务需求和场景,并进行权衡和决策。在某些情况下,可以采用其他方法来保证数据的完整性和一致性,例如使用应用程序逻辑或数据库触发器来实现约束检查和数据操作。同时,需要注意数据库设计的基本原则和最佳实践,例如避免数据冗余、遵循规范化原则和正常化理论等。
8.13 字段注释
设计表时每个字段的含义要注释清楚,包括枚举类型。比如说:
8.14 时间的类型选择
时间类型的选择一般都要好好考虑,因为不同的类型存储的格式不同。
对于MySQL来说,主要有date、datetime、time、timestamp 和 year。
| 类型 | 格式 | 范围 | 字节 |
|---|---|---|---|
| date | yyyy-mm-dd | 1000-01-01 到 9999-12-31 | 3字节 |
| time | hh:mm:ss | -838:59:59 到 838:59:59 | 3字节 |
| datetime | yyyy-mm-dd hh:mm:ss | 1000-01-01 00:00:00 到 9999-12-31 23:59:59 | 8字节,跟时区无关 |
| timestamp | yyyymmddhhmmss | 1970-01-01 00:00:01 到 2038-01-19 03:14:07 | 4字节,跟时区有关 |
| year | yyyy | 1901 到 2155 | 1字节 |
推荐优先使用datetime类型来保存日期和时间,因为存储范围更大,且跟时区无关。
8.15 SQL 编写的一些优化经验
- 避免使用SELECT * FROM 语句,应该只选择需要的列,以减少网络传输和提高查询性能
- 使用索引来提高查询速度,特别是在对大型表进行查询时
- 避免使用外键约束,因为它们可能会导致性能问题,特别是在对大型表进行插入、更新和删除操作时
- 使用LIMIT 1来限制查询结果只有一条记录
- 避免在where子句中使用OR来连接条件,应使用UNION来连接查询
- 注意优化LIMIT深分页问题,可以使用OFFSET来替代LIMIT
- 使用where条件限制要查询的数据,避免返回多余的行
- 尽量避免在索引列上使用MySQL的内置函数,这可能导致索引失效
- 应尽量避免在where子句中对字段进行表达式操作,这可能导致索引失效
- 应尽量避免在where子句中使用!=或<>操作符,这可能导致索引失效
- 使用联合索引时,注意索引列的顺序,一般遵循最左匹配原则
- 对查询进行优化,应考虑在where及order by涉及的列上建立索引
- 如果插入数据过多,考虑批量插入
- 在适当的时候,使用覆盖索引
- 使用EXPLAIN 分析你SQL的计划
9. 为什么MySQL要默认使用RR隔离级别?
9.1 隔离级别的划分
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 读未提交 | ✗ | ✗ | ✗ |
| 读已提交 | ✓ | ✗ | ✗ |
| 可重复读 | ✓ | ✓ | ✗ |
| 串行读 | ✓ | ✓ | ✓ |
✗:没有解决 ✓:已解决
一般情况下,对于 InnoDB 存储引擎,会优先使用全字段排序。可以发现 max_length_for_sort_data 参数设置为 1024,这个数比较大的。一般情况下,排序字段不会超过这个值,也就是都会走全字段排序。
