九、MySQL锁
1、全局锁(Global Lock)
(1)定义
作用于整个 MySQL 实例的锁,加锁后,整个实例只允许读,不允许任何写。
FLUSH TABLES WITH READ LOCK;
(2)解决什么问题
全局锁可以保证全库数据的一致性快照,主要用于逻辑备份。
(3)特点
会阻塞所有的INSERT/UPDATE/DELETE以及DDL。但是它不会阻塞普通的SELECT。
(4)生命周期
直到执行了
UNLOCK TABLES;
或者断开了连接,生命周期才算结束。
(5)典型场景
使用 mysqldump --single-transaction 之前(非 InnoDB)
(6)易错点
在线上的业务是禁止使用的。同时,全局锁的锁粒度最大,影响的范围也是最广的。
2、表级锁(Table Lock)
(1)定义
直接锁住整张表,任何对该表的并发访问都要受限。
(2)类型
1)表读锁(READ)
LOCK TABLE t READ;
使用表读锁时,允许多个会话都可以读,但是禁止所有会话写。
2)表写锁(WRITE)
LOCK TABLE t WRITE;
使用表写锁的时候,允许当前对话读写,禁止其他所有会话读写。
(3)使用场景
主要使用在MyISAM引擎中和一些特殊的运维场景。
(4)在InnoDB中的地位如何
在InnoDB中支持但是不推荐使用,InnoDB中主要使用和推荐使用的是行锁。
(5)易错点
表锁不等于意向锁,两者作用完全不同,表锁是可以真正"限制访问"的锁,但是意向锁只是起声明作用的。
3、意向锁(Intention Lock )
(1)定义
意向锁是表级锁,用于声明"事务将要在表中某些行上加什么类型的锁"。
(2)为什么需要
如果没有意向锁的话,会导致行锁与表锁发生冲突时,需要扫描整张表的行锁,发生性能灾难。
(3)类型
| 类型 | 含义 |
|---|---|
| IS | 将在行上加 S 锁 |
| IX | 将在行上加 X 锁 |
(4)加锁规则
在加锁的时候,需要先加意向锁,再去加锁,这一过程用户是无法手动去控制的。
(5)是否会阻塞
意向锁几乎不会出现阻塞,只用于判断表锁能不能加。
(6)易错点
意向锁不会锁数据,这也是与行锁最本质的区别,也不会影响普通的DML操作。
4、行级锁(Row Lock)
(1)记录锁(Record Lock)
**定义:**锁住索引中的某一条记录,不包含间隙
触发方式:
SELECT * FROM t WHERE id = 10 FOR UPDATE;
**特点:**可以精确锁定,而且并发度最高。
**注意:**记录锁必须走索引,否则会升级为更大范围的锁
(2)间隙锁(Gap Lock)
**定义:**间隙锁用于锁住索引记录之间的"区间",不锁具体的记录。
**解决的问题:**可以防止新行的插入,避免了幻读。
**生效条件:**在RR隔离级别且是范围查询。
**易错点:**间隙锁不会阻塞读,只会阻塞INSERT。
(3)临键锁(Next-Key Lock)
**定义:**由记录锁加上前一个间隙锁组成,这是InnoDB默认的行锁方式。
**作用:**可以同时防止当前记录被修改和区间被插入新行(即幻读)。
**易错点:**你写的"等值查询",实质上是加的临键锁(取决于索引)。
5、共享锁(S Lock)
(1)定义
允许多个事务可以读,但是禁止写。
SELECT ... LOCK IN SHARE MODE;
(2)行为
读锁与读锁是兼容的,但是读写与写锁是不兼容的。
(3)使用场景
需要保证读到的数据是不可被修改的。
6、排他锁(X Lock)
(1)定义:
排他锁是独占锁的,且禁止其他任何的读写。
SELECT ... FOR UPDATE;
(2)特点:
写操作会自动加X锁,只有当事务提交时才会释放X锁(严格2PL).
7、元数据锁(DML)
(1)定义
元数据锁是用于保护表结构的一致性的锁。
(2)行为
当你对一张表执行DML语句时,MySQL会自动在这张表上加一个MDL读锁。
当你对一张表执行DDL语句时,MySQL会自动在这张表上加一个MDL写锁。
(3)典型事故
①长事务持有MDL锁
长事务会长期持有MDL锁,不会影响其他的DML操作、SELECT,系统看着没有任何问题。MDL锁只有在提交时才会释放,如果永远不提交就永远不会释放,这张表就永远不可能被安全的修改结构。
对于下面的代码,在SELECT执行瞬间MySQL就自动给user表加MDL锁了。
SELECT * FROM user WHERE id = 1;
只要满足下面任意一条,就叫长事务:
BEGIN后 长时间不 COMMIT / ROLLBACK开启事务后执行了 SELECT
或执行了 DML
②DDL请求MDL写锁
当MySQL执行SQL前必须在执行的表上获取MDL写锁,而MDL写锁的规则是必须等所有MDL读锁释放后才能获得,于是就导致DDL被阻塞,进入MDL写锁等待队列。DDL不会失败,也不会超时,会一直等着。
③后续所有DML都被阻塞
新的 DML 进来时会发生什么?
SELECT * FROM user WHERE id = 2;
这条 DML 本来只需要 MDL 读锁,按理说读读是兼容的,对吧?
但是,因为 MySQL 的 MDL 锁有"公平队列"规则
一旦有 MDL 写锁在等待,后续任何新的 MDL 读锁请求都必须排在它后面。
原因是为了防止 DDL 被永久饿死,否则,只要源源不断的 DML,DDL 永远执行不了。
最终结果:
| 时间点 | 状态 |
|---|---|
| T1 | 长事务持有 MDL 读锁 |
| T2 | DDL 请求 MDL 写锁(等待) |
| T3 | 新 DML 请求 MDL 读锁 → 排队等待 DDL |
导致整张表的所有新DML全部阻塞。
8、自增锁(AUTO-INC Lock)
(1)定义
自增锁(AUTO-INC Lock)是 InnoDB 在生成 AUTO_INCREMENT 列值时使用的一种"表级内部锁",用于保证自增值分配的正确性和唯一性。
有几个关键的点需要注意一下:
**表级:**自增锁的作用范围是"整张表的自增计数器",而不是某一行数据。
**内部锁:**这是InnoDB引擎自己使用的锁,不暴露给SQL层,也不直接体现在用户可见的语义中。
**只为生成自增值服务:**保证AUTO_INCREMENT 值在并发环境下分配正确、唯一。
**不是行锁,也不是MDL:**不是行锁,因为行还不存在,没有row_id可以锁,它也不遵循行锁的两阶段锁协议和事务级持有;不是MDL,是因为MDL的作用是保护表结构不被并发修改;是自增锁,是因为自增锁完全不关心表结构,表结构不变但自增锁照样存在,即使没有DDL也可以频繁触发。
(2)为什么需要自增锁
①AUTO_INCREMENT 的本质问题
假设有表:
CREATE TABLE t (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20)
) ENGINE=InnoDB;
现在并发发生:
-
事务 T1:INSERT
-
事务 T2:INSERT
-
事务 T3:INSERT
那么这时候就会发现一个问题:谁用1?谁用2?谁用3?
如果不加锁,就会让多个线程同时计算"下一个自增值",这样就极容易产生重复主键。
②为什么不能用行锁?
因为在插入之前还没有那一行,也就没有行可以锁,所以只能在表级协调。所以在自增值生成阶段,必须使用一种表级同步机制自增锁。
(3)自增锁锁的是什么
自增锁并不是锁表数据,而是锁"自增值分配过程",也就是说,自增锁不会阻塞普通的SELLECT,不会阻塞UPDATE/DELETE,只影响并发INSERT中自增值的生成。
(4)加锁时机
自增锁 只在下面这个阶段存在:
InnoDB 为 INSERT 语句分配 AUTO_INCREMENT 值的瞬间
而不是整个事务生命周期。
(5)不同的INSERT形式,对自增锁的影响完全不同
①单行INSERT
INSERT INTO t(name) VALUES ('A');
特点是只需要一个自增值且自增锁持有的时间极短。并发性能很好。
②多行INSERT
INSERT INTO t(name) VALUES ('A'), ('B'), ('C');
特点是一次要分配一段连续自增值,会导致自增锁持有的时间明显变长。在高并发下容易成为瓶颈。
③INSERT ... SELECT
INSERT INTO t(name)
SELECT name FROM t2;
特点是插入行数执行前是未知的,所以InnoDB只能全程持有自增锁,直到语句结束才释放。这是自增锁导致"插入性能雪崩"的典型场景。
(6)innodb_autoinc_lock_mode
这是控制 自增锁行为策略 的关键参数。
SHOW VARIABLES LIKE 'innodb_autoinc_lock_mode';
①模式0:传统模式(Traditional)
行为:所有的INSERT都使用表级自增锁,锁持有到语句结束。
特点:自增值要求严格连续,且并发性能最差。
适用:老版本兼容,现在极少适用。
②模式 1:连续模式(Consecutive,默认)
行为:简单INSERT(VALUES)自增锁只在分配阶段短暂持有,INSERT...SELECT/批量插入这种情况任然使用表级自增锁。
特点:性能明显提升且自增值基本连续。
③模式 2:交错模式(Interleaved,推荐)
行为:完全不使用表级自增锁,通过原子操作分配自增值。
特点:并发性能最高且自增值不保证连续。
适用:适合高并发写入但是不要求自增值连续的场景。
(7)自增锁与事务回滚的关系
INSERT INTO t(name) VALUES ('A');
ROLLBACK;
自增值不会回退,因为自增值是全局计数器,不是事务资源。
所以你可能会看见:
1(回滚)
2(下一次插入)
这是设计导致的,不是bug。
9、死锁&锁等待
死锁是循环等待,InnoDB的自动检查,是回滚代价小的事务。
锁等待是非循环的,是超时抛异常的。
10、当前读和快照读有什么区别
快照读:读的是"事务开始时"或"读视图生成时"能看到的历史版本,不加行锁;
当前读:读的是"此刻数据库中最新已提交的数据",并且通常会加锁。
快照:历史版本
当前:最新版本+锁
(1)什么是快照读(Snapshot Read)
定义:
快照读是基于 MVCC,通过 Read View 读取符合可见性规则的历史版本数据的读操作。
常见的SQL形式:
SELECT * FROM t WHERE id = 1;
(没有 FOR UPDATE / LOCK IN SHARE MODE)
读的什么数据:
读的不是最新的数据,是当前事务可见的版本。也即是说,如果别的事务在你之后提交了更新,那么这个更新是看不见的。
是否加锁:
不加行锁,不阻塞写,不被写阻塞,这就是高并发性能的根本来源。
快照何时生成:
取决于隔离界别:
| 隔离级别 | Read View 生成时机 |
|---|---|
| READ COMMITTED | 每次 SELECT 都生成 |
| REPEATABLE READ(默认) | 第一次快照读时生成一次 |
(2)什么是当前读(Current Read)
定义:
当前读是直接读取记录的最新版本,并在读取时对记录加锁,保证后续操作的正确性。
触发当前读的SQL:
以下语句一定是当前读:
SELECT ... FOR UPDATE;
SELECT ... LOCK IN SHARE MODE;
UPDATE ...
DELETE ...
INSERT ...
所有的DML都是当前读,即使你没有写FOR UPDATE。
读的什么数据:
读的是当前最新版本的数据,如果其他事务未提交,则等待提交,如果已提交就读最新的。
是否加锁:
会加行锁,在RR隔离级别下,可能还会加Next-Key Lock(行锁+间隙锁)。
(3)对比
| 对比点 | 快照读 | 当前读 |
|---|---|---|
| 读的数据 | 历史版本 | 最新版本 |
| 是否加行锁 | ❌ | ✅ |
| 是否阻塞写 | ❌ | ✅ |
| 是否被写阻塞 | ❌ | ✅ |
| 使用 MVCC | ✅ | ❌(直接读当前) |
| 典型语句 | SELECT | SELECT ... FOR UPDATE / UPDATE |
(4)要点
UPDATE 在修改数据前,一定会先做一次当前读。
例如:
UPDATE t SET name='C' WHERE id=1;
这段代码的逻辑是:当前读,读取id=1的最新版本,对该行加锁,生成新版本,然后写Undo Log,所以UPDATE永远不会基于快照去该数据。
(5)和隔离级别的关系
READ COMMITTED
-
快照读:每次看到最新已提交
-
当前读:一样加锁
REPEATABLE READ(默认)
-
快照读:事务内多次 SELECT 结果一致
-
当前读:无论如何都是最新 + 加锁
隔离级别不会改变"当前读"的本质
十、MySQL性能优化
1、定位性能瓶颈
优化前必须先定位瓶颈,避免盲目操作。核心工具和方法:
(1)核心监控工具
- SHOW STATUS :查看数据库运行状态(如
Com_select/Com_insert统计 SQL 执行次数,Innodb_rows_read/Innodb_rows_updated统计行操作); - SHOW PROCESSLIST :实时查看当前连接和 SQL 执行状态(重点关注
State列,如Sending data/Locked/Creating tmp table等异常状态); - EXPLAIN/EXPLAIN ANALYZE:分析单条 SQL 执行计划(核心中的核心,下文重点讲);
- Performance Schema/Sys Schema:MySQL 5.6 + 内置的性能监控模块,可追踪 SQL 执行、锁等待、索引使用、IO 开销等;
- 慢查询日志 :开启后记录执行时间超过
long_query_time(默认 10 秒,建议设 1 秒)的 SQL,配合pt-query-digest/mysqldumpslow分析慢查询 Top; - 操作系统监控 :
top/iostat/vmstat/dstat,定位 CPU / 内存 / 磁盘 IO / 网络瓶颈(如iostat -x 1看磁盘利用率 % util 是否接近 100%)。
(2)瓶颈类型判断
| 瓶颈类型 | 典型特征 |
|---|---|
| CPU 瓶颈 | top 中 MySQL 进程 CPU 占比高,多为复杂查询(如多表关联、排序、聚合)、索引失效导致全表扫描 |
| IO 瓶颈 | iostat 显示 % util 接近 100%,InnoDB 缓冲池命中率低(Innodb_buffer_pool_reads/Innodb_buffer_pool_read_requests比值 < 99%) |
| 内存瓶颈 | 系统 Swap 频繁使用,MySQL 缓存(如缓冲池、查询缓存)命中率低 |
| 锁等待 | SHOW ENGINE INNODB STATUS 显示大量锁等待,业务出现超时 / 阻塞 |
2、索引优化------核心优化
索引是提升查询效率的关键,但不合理的索引会导致写入变慢、索引膨胀,需遵循 "按需创建、精准高效" 原则。
(1)索引基础:类型与适用场景
| 索引类型 | 适用场景 | 注意事项 |
|---|---|---|
| B + 树索引(默认) | 等值查询(=)、范围查询(>/-/<)、排序(ORDER BY)、分组(GROUP BY) | 最左前缀匹配、避免索引失效 |
| 哈希索引 | 仅等值查询(Memcached/Redis 场景更优,MySQL 仅 Memory 引擎支持) | 不支持范围查询、排序 |
| 全文索引(FULLTEXT) | 文本内容模糊匹配(如文章内容搜索) | 仅 MyISAM/InnoDB(5.6+)支持,仅适用于 CHAR/VARCHAR/TEXT |
| 空间索引(SPATIAL) | 地理坐标查询(如经纬度) | 仅 MyISAM/InnoDB(5.7+)支持 |
| 联合索引 | 多字段组合查询(如 WHERE a=1 AND b=2) | 字段顺序按 "区分度高→低" 排列,遵循最左前缀 |
(2)索引设计原则
- 最左前缀匹配 :联合索引
(a,b,c)仅匹配a、a+b、a+b+c的查询,b、b+c、a+c会失效; - 高区分度优先:区分度 = 不同值数量 / 总行数(如身份证号区分度 100%,性别仅 2%),联合索引中区分度高的字段放前面;
- 避免冗余索引 :如已有
(a,b),无需再创建(a); - 覆盖索引优先 :查询的字段都在索引中(无需回表),如
SELECT id,name FROM user WHERE age=20,创建(age,name,id)可避免回表; - 慎用索引列函数 / 运算 :如
WHERE DATE(create_time)='2025-01-01'、WHERE id+1=100会导致索引失效,需改写为WHERE create_time BETWEEN '2025-01-01 00:00:00' AND '2025-01-01 23:59:59'; - 短索引优先 :字符串字段(如手机号)可创建前缀索引(
INDEX idx_mobile (mobile(11))),减少索引占用空间; - 控制索引数量:写入操作(INSERT/UPDATE/DELETE)需要维护索引,索引越多写入越慢,单表索引建议≤5 个。
(3)索引失效的常见场景
- 使用
OR且非 OR 字段都无索引(如WHERE a=1 OR b=2,仅 a 有索引则失效); - 使用
LIKE '%xxx'(如WHERE name LIKE '%张三',LIKE '张三%'可命中索引); - 索引列使用函数 / 运算 / 类型转换(如
WHERE mobile=13800138000,mobile 为字符串,隐式转换失效); - 使用
NOT IN/!=/NOT EXISTS(部分场景优化器会放弃索引); - 数据量过小(如总行数 < 1000,优化器可能选择全表扫描);
- 联合索引违反最左前缀原则。
(4)索引维护
- 定期分析索引使用情况:
SELECT * FROM sys.schema_unused_indexes(查看未使用的索引),删除冗余 / 无用索引; - 重建碎片索引:InnoDB 表可通过
ALTER TABLE tbl_name ENGINE=InnoDB(无锁重建)或OPTIMIZE TABLE(有锁)整理索引碎片; - 避免大事务期间创建 / 删除索引:大索引创建会锁表(MySQL 8.0 支持在线 DDL,可降低影响)。
3、查询优化------从SQL降低开销
即使有索引,不合理的 SQL 仍会导致性能问题,核心是 "减少扫描行数、避免临时表 / 文件排序、降低锁竞争"。
(1)核心优化原则
- 只查需要的字段 :避免
SELECT *,减少网络传输和回表开销; - 限制结果集 :分页查询必须加
LIMIT,避免全表扫描返回大量数据; - 避免大表关联 :多表关联时,小表驱动大表(InnoDB 嵌套循环连接优化),如
SELECT * FROM small_tbl s JOIN big_tbl b ON s.id = b.sid; - 减少子查询 :子查询易产生临时表,改写为 JOIN(如
SELECT * FROM user WHERE id IN (SELECT user_id FROM order WHERE status=1)→SELECT u.* FROM user u JOIN order o ON u.id=o.user_id WHERE o.status=1); - 避免临时表 / 文件排序 :
ORDER BY/GROUP BY字段未命中索引时,会创建临时表(Using temporary)或文件排序(Using filesort),需优化索引覆盖排序字段; - 批量操作替代循环单条 :批量 INSERT(
INSERT INTO tbl (a,b) VALUES (1,2),(3,4),(5,6))、批量 UPDATE/DELETE,减少连接和日志刷盘开销; - 慎用 DISTINCT/UNION:DISTINCT 可通过索引去重优化,UNION(去重)改为 UNION ALL(不去重)(如无重复数据);
- 拆分复杂查询:将一个复杂 SQL 拆分为多个简单 SQL,降低单次查询的 CPU/IO 开销。
(2)EXPLAIN 分析执行计划(核心)
EXPLAIN SELECT ... 输出 12 列核心字段,重点关注:
| 字段 | 关键解读 |
|---|---|
| id | 查询执行顺序(数字越大越先执行,相同数字按顺序执行) |
| select_type | SIMPLE(简单查询)、DERIVED(派生表)、SUBQUERY(子查询)、DERIVED(派生表)等,复杂类型需优化 |
| type | 访问类型(从优到差:system > const > eq_ref > ref > range > index > ALL),至少保证 range,避免 ALL(全表扫描) |
| key | 实际使用的索引,NULL 表示未使用索引 |
| rows | 优化器预估扫描行数,行数越少越好 |
| Extra | 关键提示:- Using index:覆盖索引(优);- Using where:过滤条件(正常);- Using temporary:创建临时表(需优化);- Using filesort:文件排序(需优化);- Using join buffer:连接缓冲区(大表关联需调大 join_buffer_size)。 |
示例:
EXPLAIN SELECT id,name FROM user WHERE age=20 AND gender=1 ORDER BY create_time;
-- 若type为ALL,key为NULL,Extra有Using filesort,需创建联合索引(age,gender,create_time)
4、配置调优
MySQL 默认配置偏保守,需根据服务器硬件(CPU / 内存 / 磁盘)和业务场景(读多 / 写多 / 混合)调整my.cnf/my.ini核心参数。
(1)核心参数(InnoDB 为主)
| 参数 | 作用 | 建议值(参考 8 核 16G 服务器) |
|---|---|---|
| innodb_buffer_pool_size | InnoDB 缓冲池(缓存数据和索引),核心参数 | 物理内存的 50%-70%(如 16G 内存设 10G) |
| innodb_log_file_size | 重做日志文件大小,影响事务提交性能 | 1-4G(总日志大小 innodb_log_file_size*innodb_log_files_in_group ≤ 8G) |
| innodb_log_buffer_size | 重做日志缓冲区 | 64M-256M(写密集场景调大) |
| innodb_flush_log_at_trx_commit | 日志刷盘策略 | 1:事务提交立即刷盘(最安全,性能略低);0/2:每秒刷盘(性能高,宕机可能丢 1 秒数据) |
| sync_binlog | 二进制日志刷盘策略 | 1:每次事务提交刷盘(安全);100:每 100 次事务刷盘(性能高) |
| max_connections | 最大连接数 | 业务峰值连接数 + 预留(如 1000-2000,避免过大导致内存溢出) |
| wait_timeout/interactive_timeout | 连接超时时间 | 300 秒(避免闲置连接占用资源) |
| query_cache_type/query_cache_size | 查询缓存(MySQL 8.0 已移除) | 读多写少场景开启,设 64M-128M;写密集场景关闭(query_cache_type=0) |
| join_buffer_size | 连接缓冲区(多表关联) | 2M-8M(按需调大,避免全局设太大) |
| sort_buffer_size | 排序缓冲区 | 2M-8M(每个连接独立分配,避免全局设太大) |
| read_buffer_size/read_rnd_buffer_size | 顺序 / 随机读缓冲区 | 2M-8M |
| tmp_table_size/max_heap_table_size | 临时表大小(内存临时表上限) | 64M-128M(避免临时表写入磁盘) |
| innodb_io_capacity/innodb_io_capacity_max | 磁盘 IO 能力上限 | 根据磁盘性能设置(SSD 设 2000,HDD 设 200) |
| innodb_flush_method | 刷盘方式 | O_DIRECT(绕过操作系统缓存,SSD 推荐) |
(2)配置优化原则
- 避免参数 "一刀切":读密集场景调大缓冲池、查询缓存;写密集场景调大日志缓冲区、刷盘策略放宽;
- 监控参数有效性:通过
SHOW GLOBAL STATUS查看参数命中率(如缓冲池命中率 = 1 - Innodb_buffer_pool_reads/Innodb_buffer_pool_read_requests); - 逐步调整:单次改 1-2 个参数,观察性能变化,避免批量修改导致故障。
5、存储引擎与表结构优化
(1)存储引擎选择
| 引擎 | 适用场景 | 注意事项 |
|---|---|---|
| InnoDB | 绝大多数场景(事务、行锁、崩溃恢复、外键) | 优先选择,MySQL 5.5 + 默认引擎 |
| MyISAM | 只读 / 读多写少(如日志、统计) | 无事务、表锁,崩溃恢复差,已逐步淘汰 |
| Memory | 临时数据、缓存(如会话数据) | 数据易失,不支持大表 |
(2)表结构优化
- 字段类型最小化:如年龄用 TINYINT(1 字节)而非 INT(4 字节),手机号用 CHAR (11) 而非 VARCHAR (20),时间用 DATETIME 而非 VARCHAR;
- 避免 NULL 值:NULL 值会增加索引 / 存储开销,可设默认值(如空字符串、0);
- 拆分大表:- 垂直拆分:将大字段(如 TEXT/BLOB)拆到子表(如用户表拆 user_base(基础信息)+ user_ext(简介 / 头像));- 水平拆分:按时间 / 地域 / 哈希拆分(如订单表按年拆 order_2024、order_2025);
- 使用分区表 :对大表(千万级以上)按范围 / 列表 / 哈希分区(如按时间分区
PARTITION BY RANGE (TO_DAYS(create_time))),减少扫描范围; - 避免使用外键:外键会增加写入开销,业务层控制关联关系;
- 使用自增主键:InnoDB 主键为聚簇索引,自增主键可避免页分裂,提升写入性能(避免 UUID 作为主键,离散写入导致索引碎片)。
6、硬件与操作系统优化
(1)硬件选型
- CPU:多核 CPU(MySQL 对多核支持较好,复杂查询 / 高并发需 8 核以上);
- 内存:越大越好(优先满足 InnoDB 缓冲池);
- 磁盘:SSD(IOPS 比 HDD 高 10 倍以上)> NVMe SSD,避免使用机械硬盘;
- 网络:10Gbps 网卡,避免网络带宽成为瓶颈(如大数据量导出 / 导入)。
(2)操作系统优化
- 文件系统:XFS(比 EXT4 更适合大文件、高 IO);
- 磁盘调度策略 :SSD 设
mq-deadline,HDD 设deadline(echo mq-deadline > /sys/block/sda/queue/scheduler); - 关闭 swap :避免内存交换(
swapoff -a,永久关闭需修改 /etc/fstab); - 调整文件描述符限制 :
ulimit -n 65535(避免 Too many open files 错误); - TCP 参数优化 :调整
net.core.somaxconn(监听队列)、net.ipv4.tcp_tw_reuse(复用 TIME_WAIT 连接)等,提升网络并发。
XFS 是一个由 SGI 开发的 64 位高性能日志型文件系统 ,专为大文件、大并发、高吞吐场景设计。
7、架构层面优化
当单库单表性能达到瓶颈,需从架构层面扩展:
(1)读写分离
- 主库(Master)负责写入,从库(Slave)负责读取,通过复制(Replication)同步数据;
- 常用方案:ProxySQL/MaxScale/MyCat 中间件,或业务层读写分离;
- 注意:复制延迟问题(需监控 Seconds_Behind_Master,写后读场景需特殊处理)。
(2)分库分表
- 单表数据量超千万 / 亿级时,拆分表(水平 / 垂直)、拆分库(按业务模块);
- 常用中间件:ShardingSphere/MyCat/TDDL;
- 核心:分片键选择(如订单表按用户 ID 哈希分片、按时间范围分片),避免跨分片查询。
(3)缓存层引入
- 热点数据缓存:Redis/Memcached 缓存查询结果(如商品详情、用户信息),减少数据库访问;
- 注意:缓存穿透 / 击穿 / 雪崩问题,需配合布隆过滤器、过期时间随机化、缓存预热等策略。
(4)读写优化补充
- 批量写入:合并小事务,减少日志刷盘次数;
- 延迟写入:非核心数据(如日志、统计)异步写入(消息队列 + 批量入库);
- 只读实例:针对报表 / 分析类查询,部署只读实例,避免影响主业务。