数据库Mysql
基础概念:数据库、表、字段、数据类型等。
SQL 语句:基础的增删改查,以及高级的索引使用和查询优化。
事务与并发:ACID 特性、隔离级别、锁机制等。
架构与引擎:
InnoDB
与MyISAM
的区别,底层原理等。性能优化:如何分析和优化慢查询。
1. 数据库、表和模式(Schema)有什么区别?
数据库(Database):是物理上的集合,包含了表、视图、存储过程等对象的容器。
表(Table):是数据库中最基本的数据结构,由行(记录)和列(字段)组成。
模式(Schema):在 MySQL 中,模式和数据库是同一个概念。但在其他数据库中,模式可以看作是数据库中的一个逻辑命名空间,用于管理表、视图等对象。
2. VARCHAR
和 CHAR
有什么区别?
CHAR
:固定长度字符串。当存储的数据长度小于定义的长度时,会用空格填充。查询时会去除尾部空格。它的存取速度比VARCHAR
快,但会浪费存储空间。VARCHAR
:可变长度字符串。只存储实际的字符串,不会填充。存储空间更节省,但存取速度比CHAR
略慢。
选择建议:如果存储的字符串长度固定且较短(如 MD5
值),使用 CHAR
;如果长度可变,则使用 VARCHAR
。
3. DELETE
、TRUNCATE
和 DROP
有什么区别?
命令 | 描述 | 是否可以回滚 | 是否重置自增 ID |
DELETE | 删除表中的行,每次删除一行。 | 可以回滚。 | 不会重置。 |
TRUNCATE | 删除表中的所有行,但保留表结构。 | 不可以回滚。 | 会重置。 |
DROP | 删除整个表(包括表结构和所有数据)。 | 不可以回滚。 | 会删除。 |
执行速度:DROP
> TRUNCATE
> DELETE
。
4. 什么是索引?有什么优缺点?
索引是一种特殊的数据结构,可以帮助数据库系统快速查询数据。它类似于书的目录,让你能迅速定位到所需内容。
优点:
加快查询速度:显著提升
SELECT
语句的查询效率。保证唯一性:
UNIQUE KEY
保证表中某一列或多列数据的唯一性。
缺点:
占用空间:索引本身需要额外的存储空间。
降低写入速度:当对表进行
INSERT
、UPDATE
、DELETE
操作时,索引也需要同步维护,会降低操作速度。
5. PRIMARY KEY
、UNIQUE KEY
和 FOREIGN KEY
有什么区别?
PRIMARY KEY
(主键):唯一标识表中每一条记录。
值不能重复,且不能为 NULL。
每张表只能有一个主键。
UNIQUE KEY
(唯一键):保证该列或多列组合的值是唯一的。
可以有多个
NULL
值。一张表可以有多个唯一键。
FOREIGN KEY
(外键):用于建立表之间的关联。
外键列的值必须在主表的主键列中存在。
6. 什么是事务?ACID 是什么?
事务是一系列数据库操作的集合,这些操作要么全部成功,要么全部失败。
ACID 是事务的四大特性:
原子性(Atomicity):事务是一个不可分割的整体,要么都执行,要么都不执行。
一致性(Consistency):事务执行前后,数据库从一个一致性状态变为另一个一致性状态。
隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
持久性(Durability):事务提交后,对数据库的修改是永久性的,即使系统崩溃也不会丢失。
7. 事务的隔离级别有哪些?
MySQL 提供了四种事务隔离级别,级别从低到高,安全性越来越高,但并发性能越来越差:
Read Uncommitted(未提交读):能读取到其他事务未提交的数据,会产生脏读。
Read Committed(提交读):只能读取到其他事务已提交的数据,解决了脏读,但会产生不可重复读。
Repeatable Read(可重复读):在同一个事务中,多次读取同一行数据,结果始终一致。解决了不可重复读,但会产生幻读。
Serializable(串行化):最高隔离级别,所有事务串行执行,完全杜绝了并发问题,但性能最差。
MySQL 默认的隔离级别是 Repeatable Read
。
8. 脏读、不可重复读和幻读是什么?
脏读(Dirty Read):一个事务读取了另一个未提交的事务修改的数据。
不可重复读(Non-repeatable Read):一个事务两次读取同一行数据,但结果不一致。这是因为另一个已提交的事务对该行数据进行了修改。
幻读(Phantom Read):一个事务两次执行相同的查询,但第二次查询的结果集多出了或少了一些新插入或新删除的行。
9. InnoDB
和 MyISAM
有什么区别?
特性 | InnoDB | MyISAM |
事务 | 支持(ACID) | 不支持 |
锁级别 | 行级锁 | 表级锁 |
外键 | 支持 | 不支持 |
适用场景 | 事务性操作,并发性高,数据完整性要求高。 | 读操作多,对事务和完整性要求不高。 |
InnoDB
是 MySQL 5.5.5 版本以后的默认存储引擎。
10. B-Tree
索引和哈希(Hash)索引有什么区别?
B-Tree
索引:结构:一种平衡多路查找树。
特点:可以用于等值查询(
=
)、范围查询(>
、<
、BETWEEN
)和模糊查询(LIKE 'xxx%'
)。适用:是绝大多数索引的默认类型。
哈希索引:
结构:基于哈希表实现。
特点:只能用于等值查询,不能用于范围查询。因为哈希值是无序的。
适用:只有在等值查询非常频繁且数据量大时,才会考虑使用。
11. 什么是复合索引?如何选择字段顺序?
复合索引是在多个列上创建的索引。它的查询效率高于单列索引。
选择顺序:遵循最左前缀原则。将最常用于查询且最具选择性(即重复值少)的列放在最左侧。
例如,索引 (a, b, c)
可以用于以下查询:
WHERE a = ? AND b = ? AND c = ?
WHERE a = ? AND b = ?
WHERE a = ?
12. 什么是覆盖索引?
覆盖索引(Covering Index) 指查询所需的字段都包含在索引的 B-Tree 节点中,因此数据库无需回表查询主数据。这能显著提高查询性能。
判断:如果 EXPLAIN
的 Extra
列显示 Using index
,则说明使用了覆盖索引。
13. 如何发现和优化慢查询?
发现:
慢查询日志:在 MySQL 配置中开启慢查询日志,并设置一个阈值(如超过 1 秒)。
EXPLAIN
:用于分析 SQL 查询的执行计划,是优化 SQL 的核心工具。
优化:
建立合适的索引:这是最重要的优化手段。
重写 SQL:简化查询逻辑,避免使用
*
,只查询需要的字段。避免全表扫描:在
WHERE
子句中使用索引,避免在索引列上进行函数运算。
14. 解释 EXPLAIN
语句的输出,特别是 type
和 key
。
EXPLAIN
用于分析 SQL 的执行计划。
type
:表示访问类型,是衡量查询好坏的最重要指标。const
:通过主键或唯一索引直接定位,最快。ref
:通过非唯一索引查找,返回匹配的行。range
:范围查询。index
:全索引扫描。ALL
:全表扫描,性能最差。
key
:表示 MySQL 实际使用的索引。
15. 什么是数据库范式?为什么需要它?
范式(Normalization) 是一种数据库设计规范,用于消除数据冗余,提高数据完整性。
优点:
减少数据冗余:节省存储空间。
避免数据更新异常:避免在修改时因数据不一致而引发的问题。
提高数据完整性。
最常用的范式是第三范式(3NF)。
16. 什么是 InnoDB
的 MVCC?
MVCC(多版本并发控制) 是 InnoDB
存储引擎实现非阻塞读的核心机制。它通过在每行数据上维护多个版本,来解决读写冲突。
工作原理:每行数据都有一个隐藏的版本号和事务 ID。当一个事务读取数据时,它会读取一个符合其版本号的旧版本数据,而不会被其他正在进行的写事务阻塞。这样,读操作和写操作就可以并发执行。
17. 数据库锁有哪些?
行锁:锁定表中某一行或多行数据。
InnoDB
支持行锁,能最大程度支持并发。表锁:锁定整个表。
MyISAM
仅支持表锁,在写入时,整个表都会被锁住。
18. 什么是数据库乐观锁和悲观锁?
悲观锁(Pessimistic Locking):假设并发冲突必然发生,因此在操作数据前先独占式地锁定资源。
实现:通常使用数据库的锁机制,如
SELECT ... FOR UPDATE
。
乐观锁(Optimistic Locking):假设并发冲突不常发生,在数据操作时不加锁,而是在提交更新时通过版本号或时间戳来检查是否冲突。
实现:在表中增加一个版本号(
version
)或时间戳字段。
19. 索引的底层数据结构是什么?为什么是 B-Tree
?
绝大多数数据库(包括 MySQL)的索引都使用 B+ Tree
(B-Tree 的变种)作为底层数据结构。
B+ Tree
的优点:
降低磁盘 I/O:树的扇出(分支)很大,使得树的高度很低,查询一个节点只需要很少的磁盘 I/O 次数。
支持范围查询:所有叶子节点都通过指针连接,方便进行范围查找。
数据都在叶子节点:所有数据都存储在叶子节点,有利于全表扫描。
20. 数据库连接池的作用?
数据库连接池是一个存放预先创建好的数据库连接的“池子”。
作用:
减少开销:避免了频繁创建和关闭数据库连接的资源消耗。
提高性能:请求可以立即从连接池中获取连接,无需等待创建。
统一管理:可以限制连接数,防止因并发过高而导致数据库崩溃。
1. UNION
和 UNION ALL
有什么区别?
UNION
: 对结果集进行去重,并对结果排序。如果两个结果集有重复行,UNION
只会保留一行。这个过程需要额外的 CPU 和内存开销。UNION ALL
: 直接合并两个结果集,不做去重和排序。速度更快,因为它省去了去重和排序的步骤。
使用场景:如果确保结果集没有重复,或不需要去重,始终使用 UNION ALL
以获得更好的性能。
2. JOIN
、LEFT JOIN
和 RIGHT JOIN
有什么区别?
INNER JOIN
: 也叫JOIN
,只返回两个表中都存在匹配关系的行。LEFT JOIN
: 返回左表的所有行,以及右表中匹配的行。如果右表中没有匹配项,则右表的列会显示NULL
。RIGHT JOIN
: 返回右表的所有行,以及左表中匹配的行。
3. DATETIME
和 TIMESTAMP
有什么区别?
DATETIME
:存储范围:
1000-01-01
到9999-12-31
。存储空间:8 个字节。
时区:不依赖时区,存储的是字面上的时间。
TIMESTAMP
:存储范围:
1970-01-01
到2038-01-19
。存储空间:4 个字节。
时区:依赖时区。它将时间转换为 UTC 格式进行存储,并在检索时再转换回当前时区,适合国际化应用。
4. 如何优化带有 LIKE
的查询?
LIKE
查询通常会使索引失效。优化原则是:
避免使用前缀模糊匹配:
LIKE '%keyword%'
会导致全索引或全表扫描,索引失效。使用后缀模糊匹配:
LIKE 'keyword%'
可以有效利用索引。使用全文索引:对于需要进行全文搜索的场景,应使用全文索引(
FULLTEXT
),而不是LIKE
。
5. 什么是 MySQL 复制(Replication)?
MySQL 复制是一种异步的数据同步机制,用于将数据从一个 MySQL 数据库(主库)同步到一个或多个其他 MySQL 数据库(从库)。
作用:
读写分离:主库负责写,从库负责读,提高并发能力。
高可用:当主库故障时,可以快速切换到从库。
数据备份:从库可以作为主库的备份。
6. 什么是二进制日志(binlog)?
binlog 是 MySQL 记录所有数据更改操作(增、删、改)的二进制文件。它是 MySQL 复制的核心,从库通过读取和执行主库的 binlog 来实现数据同步。
7. 什么是主从延迟(Master-Slave Delay)?如何解决?
主从延迟是指主库执行完一个操作后,从库需要一段时间才能同步该操作。
原因:网络延迟、从库执行慢 SQL 等。
解决方法:
优化慢 SQL:确保从库的 SQL 执行效率。
读写分离:如果对实时性要求高,将强一致性要求高的读操作放在主库。
升级硬件:增加从库的硬件配置。
8. 什么是逻辑备份和物理备份?
逻辑备份:通过导出 SQL 语句来备份数据。例如
mysqldump
工具。优点:文件小,可读性好,跨版本和跨平台恢复。
缺点:恢复速度慢。
物理备份:直接复制数据库文件进行备份。例如
xtrabackup
工具。优点:备份和恢复速度快,适合海量数据。
缺点:文件大,只能在相同版本和系统上恢复。
9. 简述 MySQL 查询执行的过程。
客户端发送 SQL 语句。
连接器:验证身份和权限。
查询缓存:如果开启,检查缓存中是否有结果,有则直接返回。
分析器:对 SQL 语句进行语法和词法分析。
优化器:生成最佳的执行计划,选择使用哪个索引。
执行器:根据优化器的执行计划,调用存储引擎接口,获取数据并返回。
10. InnoDB
的缓冲池(Buffer Pool)是什么?
缓冲池是 InnoDB
存储引擎在内存中分配的一块区域,用于缓存表数据和索引数据。
作用:当查询需要数据时,首先在缓冲池中查找,如果命中,则直接返回,避免了昂贵的磁盘 I/O 操作,极大地提高了性能。
11. 什么是聚簇索引(Clustered Index)?
聚簇索引是一种特殊的索引,它将索引的 B+ Tree 和实际的数据行存储在一起。
特点:
每张表只能有一个聚簇索引。
通常是主键,如果没有主键,MySQL 会选择一个非空唯一索引作为聚簇索引,否则会自动创建一个隐藏的聚簇索引。
优点:通过聚簇索引查询数据时,可以直接在索引树上找到所有数据,无需再次回表查询。
12. 什么是二级索引(Secondary Index)?
二级索引也叫非聚簇索引,它将索引和数据分开存储。
特点:二级索引的叶子节点存储的不是完整的数据行,而是主键值。
查询过程:通过二级索引查找到主键值后,再通过主键值去聚簇索引中找到完整的行数据,这个过程被称为回表。
13. 什么是共享锁(Shared Lock)和排他锁(Exclusive Lock)?
共享锁(S 锁):允许多个事务同时对同一资源加锁,主要用于读操作。
排他锁(X 锁):只允许一个事务对资源加锁,其他事务不能再加任何锁,主要用于写操作。
14. InnoDB
如何解决幻读?
InnoDB
在 Repeatable Read
隔离级别下,通过**间隙锁(Gap Lock)**来解决幻读。
间隙锁:它锁定的是索引中的一个间隙(Gap),而不是具体的数据行。这样,其他事务就无法在该间隙中插入新的数据。
15. 什么是存储过程(Stored Procedure)?
存储过程是一段预先编译并存储在数据库中的 SQL 语句集合。
优点:
性能好:编译一次,多次执行,减少了网络通信和编译开销。
模块化:将复杂的业务逻辑封装在数据库中。
16. utf8
和 utf8mb4
有什么区别?
utf8
:是 MySQL 早期实现的字符集,它只支持最多 3 个字节的 UTF-8 编码。utf8mb4
:是 MySQL 真正的 UTF-8 字符集,支持最多 4 个字节的 UTF-8 编码。
区别:utf8mb4
支持存储表情符号等 4 字节的 Unicode 字符,而 utf8
则不支持。因此,在任何需要支持表情符号的场景,必须使用 utf8mb4
。
17. 如何优化分页查询 LIMIT
和 OFFSET
?
当 OFFSET
值很大时,LIMIT offset, count
的查询效率会非常低,因为它需要扫描并丢弃大量的数据。
优化方案:
子查询优化:先通过子查询找到要查询的主键 ID,然后通过主键 ID 查询所有字段。
SQLSELECT * FROM orders WHERE id IN (SELECT id FROM orders ORDER BY created_at DESC LIMIT 100000, 10 ) ORDER BY created_at DESC;
记录上一次的游标:在前端记录上一次查询的最后一条记录的 ID 或时间,下次查询时直接从这个点开始。
SQLSELECT * FROM orders WHERE id > last_id LIMIT 10;
18. 数据库分库分表有哪些方式?
垂直分库:按业务拆分,将不同业务的数据放在不同的数据库中。
垂直分表:将一个大表按列拆分,将不常用的列单独放在一个表中。
水平分库分表:按某个字段(如用户 ID)将数据均匀分布到不同的库和表中。
19. 什么是表碎片?如何处理?
表碎片是由于频繁的 INSERT
、UPDATE
、DELETE
操作,导致数据存储不连续,从而降低查询性能。
处理方法:
OPTIMIZE TABLE
命令可以对表进行碎片整理。
20. 什么是索引下推(Index Condition Pushdown)?
索引下推是 MySQL 5.6 引入的一项优化,它在存储引擎层进行数据过滤,而不是等到服务器层。
原理:当索引包含
WHERE
子句中的所有列时,存储引擎会先根据索引过滤不满足条件的行,再返回给服务器层。这可以减少回表次数,提高查询性能。
MySQL 如何实现分布式锁
MySQL 本身并不是一个专门的分布式锁服务,但可以利用其特性来简单地实现锁。您提到的两种方法是常见的实现方式。
方法一:利用唯一索引的冲突特性
这是一个基于数据库悲观锁的实现。
实现原理:
创建一个专门的锁表,包含一个唯一索引的字段(如
lock_key
)。当线程 A 想要获取锁时,向该表插入一条记录,其
lock_key
值为锁名。如果插入成功,则表示获取锁成功。
如果插入失败(因为唯一索引冲突),则表示锁已被其他线程持有,获取失败。
线程 A 释放锁时,删除该条记录。
为防止死锁,可以添加一个
expire_time
字段,通过定时任务清理过期的锁。
方法二:利用 MySQL 内置函数 GET_LOCK()
这是 MySQL 提供的用户级锁,它在单个 MySQL 实例内工作。
实现原理:
GET_LOCK(lock_name, timeout)
:尝试获取名为lock_name
的锁,并等待timeout
秒。如果成功获取锁,返回 1;如果超时,返回 0;如果发生错误,返回
NULL
。锁会在当前会话(Session)结束时自动释放,或者通过调用
RELEASE_LOCK(lock_name)
手动释放。需要注意的是,这个锁只在单个 MySQL 实例内有效,无法跨多个 MySQL 实例实现真正的分布式锁。
2. UPDATE
语句什么时候从行级锁升级为表级锁
InnoDB
存储引擎默认使用行级锁,这是其高并发的核心。但某些情况下,为了保证数据一致性,InnoDB
会使用表级锁。
UPDATE
语句并不会像一些数据库那样发生锁升级(Lock Escalation)——即从行锁动态变为表锁。但当满足以下条件时,UPDATE
语句会隐式地使用表级锁或等同于表级锁的效果:
没有使用索引:当
UPDATE
语句的WHERE
条件中没有使用任何索引,或者索引失效,导致MySQL
必须进行全表扫描时,它无法确定要更新哪些行,因此会锁住整个表。使用了不当的索引:如果
WHERE
条件中的索引无法有效过滤数据,例如使用了范围查询或模糊匹配,MySQL
可能会锁定整个索引树或大范围的数据,从而阻塞其他操作。锁住的行数过多:虽然
InnoDB
使用的是行级锁,但如果一个事务更新的行数过多,其管理的锁资源开销会变得非常大,实际上也会导致其他事务无法进入,效果上与表锁类似。
MySQL 优化手段
表分区(Table Partitioning)
表分区是将一个大表在逻辑上划分为更小、更易于管理的分区,但从用户的角度看,它仍然是一个完整的表。
分区类型:常见的分区方式包括按范围(
RANGE
)、按列表(LIST
)、按哈希(HASH
)和按键值(KEY
)等。优点:
查询性能提升:如果查询条件包含分区键,
MySQL
只会扫描相关的分区,这个过程称为分区裁剪(Partition Pruning),大大减少了扫描的数据量。维护更简单:可以快速删除或归档老旧数据(如
TRUNCATE PARTITION
),而不需要执行耗时的DELETE
操作。
跨区查询
当查询条件包含分区键时,MySQL
会自动执行分区裁剪,只在特定分区中进行查询。
示例:如果表按日期分区,查询
WHERE date = '2023-01-01'
,MySQL
只会去扫描2023-01-01
所在的分区。跨区查询:如果查询条件没有分区键,或者查询范围跨越了多个分区,
MySQL
会扫描所有相关的分区。虽然性能比全表扫描好,但仍需要注意优化。
跨表查询
跨表查询通常指在多个独立的表之间进行关联查询,最常见的方式是使用 JOIN
。
优化手段:
建立索引:确保
ON
子句中的关联字段都建立了索引,这是JOIN
性能优化的核心。选择合适的
JOIN
类型:根据实际业务选择INNER JOIN
、LEFT JOIN
或RIGHT JOIN
,避免不必要的扫描。避免大表
JOIN
大表:如果可能,应先对其中一个表进行过滤,减少需要JOIN
的行数。
优化前:使用嵌套子查询
最初的查询使用了子查询和临时表,逻辑复杂且效率低下。数据库需要先执行子查询,生成一个临时结果集,然后才能进行外层查询,这通常会导致全表扫描或无法有效利用索引。
SQL
SELECTuser_id,SUM(total_amount) AS total_spent
FROMorders
WHEREuser_id IN (SELECT user_idFROM ordersWHERE created_at >= CURDATE() - INTERVAL 30 DAYGROUP BY user_idORDER BY COUNT(*) DESCLIMIT 50)
GROUP BYuser_id
ORDER BYtotal_spent DESC;
问题分析:这个查询的执行计划会非常糟糕。数据库需要为子查询创建一个庞大的临时表,再与外部查询进行匹配,导致 CPU 和内存消耗巨大。
优化后:使用窗口函数和简化聚合
通过重写查询,使用窗口函数和更直接的聚合,可以将逻辑简化,并让数据库能够一次性处理所有数据。
SQL
SELECTuser_id,SUM(total_amount) AS total_spent
FROMorders
WHEREcreated_at >= CURDATE() - INTERVAL 30 DAY
GROUP BYuser_id
ORDER BYCOUNT(*) DESC
LIMIT 50;
优化原理:
简化逻辑:新查询移除了嵌套的子查询,直接对
orders
表进行WHERE
筛选和GROUP BY
聚合。高效聚合:数据库可以直接在一个查询中完成所有操作,避免了创建昂贵的临时表,使得它能够更好地利用索引,从而将查询效率提升数倍。
示例二:建立复合索引
场景:物流系统的订单状态追踪
一个物流团队的仪表盘需要频繁查询某个**特定产品(product_id
)在某个时间段(order_date
)内,处于“已发货”状态(delivery_status
)**的所有订单。
优化前:无有效索引的查询
假设 orders
表有数亿条记录,且在 product_id
、order_date
和 delivery_status
上都没有合适的索引。
SQL
SELECTorder_id,customer_id
FROMorders
WHEREproduct_id = 12345AND order_date BETWEEN '2023-01-01' AND '2023-01-31'AND delivery_status = 'shipped';
问题分析:数据库不得不对整张表进行全表扫描,逐行检查是否满足所有 WHERE
条件。随着数据量增长,查询时间会越来越长。
优化后:建立复合索引
根据查询条件,建立一个复合索引,将最常用且最具区分度的字段放在前面。
SQL
CREATE INDEX idx_prod_date_status
ON orders (product_id, order_date, delivery_status);
优化原理:
索引顺序:索引字段的顺序至关重要。我们将最精确的条件
product_id
放在首位,以便快速定位到少数几条记录。然后是范围查询条件order_date
,最后是delivery_status
。高效查找:有了这个索引,数据库可以像查字典一样,通过
product_id
快速找到一个很小的子集,然后在该子集内通过order_date
进一步过滤,最后筛选delivery_status
。这种方式避免了全表扫描,将查询时间从数秒缩短到毫秒级。