一,Insert优化
1.1 SQL语句优化
-
批量插入(Bulk Insert)
INSERT INTO table (col1, col2) VALUES (v1, v2), (v3, v4), ...;- 单次插入多条数据(建议500~2000条/批)
- 减少网络传输和SQL解析开销
-
LOAD DATA INFILE
LOAD DATA INFILE '/path/data.csv' INTO TABLE table;- 比普通INSERT快20倍以上
- 绕过SQL解析层直接导入
-
禁用自动提交
START TRANSACTION; INSERT...; INSERT...; COMMIT;- 将多个INSERT包裹在事务中
- 减少磁盘刷写次数
1.2 存储引擎调优
InnoDB优化:
-
配置参数调整:
innodb_buffer_pool_size = 系统内存的70-80% innodb_flush_log_at_trx_commit = 2 # 批量刷盘 innodb_log_file_size = 4G # 更大的redo日志 innodb_autoinc_lock_mode = 2 # 交错自增锁模式 -
页压缩:
CREATE TABLE ... ROW_FORMAT=COMPRESSED;
MyISAM适用场景:
-
只用于插入密集型、非事务需求场景
-
配置调整:
concurrent_insert = 2 # 允许并发插入 bulk_insert_buffer_size = 256M
二,主键优化
2.1 索引组织表(IOT)
在innodb引擎中表数据是根据主键顺序存放的。这种存储关系叫索引组织表(IOT)
如上图,就是以主键顺序存放的索引组织表。
- 叶子节点存放数据,非叶子节点起到索引的作用。
- 这些黄色的块全部是由逻辑层次结构页(page)组成的。
- 页是innodb磁盘管理的最小单元,最大16k
- 页内数据根据主键顺序插入
2.2 页分裂
页可以为空,可以存一半,可以充满,每一个页知识包含2行数据,如果某一行数据过大会产生行溢出现象。
2.2.1 主键顺序插入的流程图
-
插入id=1的数据
-
插入id=2的数据
-
插入id=3的数据
-
插入id=4的数据
-
插入id=5的数据
-
插入id=6的数据
-
插入id=7的数据
-
插入id=8的数据
-
当第一个数据页写满后,想插入9就先申请第二个页并插入9,且将他们之间维护一个双向指针
-
将第二页写满
2.2.2 主键乱序插入的流程图
-
假定已经存放如下数据
-
这时我们想插入一条id=50的数据,本应该插入到47和55之间,但是他们已经满了。
故先开辟新的数据页3,因为在第一页中23和47占了页的一半,故移动23和47到新的页3去,然后将50插入到47后
-
最后重构链表指针
2.3 页合并
假定当前叶子节点的情况如图
当删除一行记录时,实际上并没有被物理删除,只是被标记为删除并且他的空间变得运行其他记录声明使用。
当页中删除的记录达到 MERGE_THRESHOLD(默认为页的50%),InnoDB会开始寻找最靠近的页(前或后)看看是否可以将俩个页合并从而优化空间使用。
最终会变成如图
MERGE_THRESHOLD页合并参数的阈值,可以自己设置,在创建表或者索引的时候指定即可。
MERGE_THRESHOLD参数设置过程
-
创建表的时候设置
create table 表名( 字段1 类型, ... 字段n 类型 )comment='MERGE_THRESHOLD=45'; -
修改
ALTER TABLE t1 COMMENT='MERGE_THRESHOLD=40';
2.4 主键设计原则
-
尽量降低主键的长度
理由:在聚集索引和二级索引中,如果二级索引很多并且,主键索引很长那么会占用很大的空间,搜索的时候会降低大量的磁盘io。
-
插入数据的时候尽量使用顺序插入,auto_increment约束
理由: 如果乱序插入会可能出现页分裂现象
-
业务操作的时候避免对主键的修改
三,Order by优化
explain中的extra会出现2种情况在order by中
-
using filesort
通过表的扫描或者索引,读取满足条件的数据行,然后在排序缓冲区(sort buffer) 中完成排序操作,所有不是通过索引直接返回排序结构的叫
using filesort,需要查出来后额外排序,效率低。 -
using index
通过有序索引顺序扫描直接返回有序结构,不需要额外排序操作,效率高。
将using filesort优化成using index的办法:在创建索引的时候规定排序方式
create index 索引名 on 表名(字段1 排序方式,字段2 排序方式...);
注意要点: 记得遵循索引规则
- 如果无法避免必须使用using filesort,可以增加
sort_buffer_size区的大小(默认256kb)
查询sort_buffer_size的大小
show variables like 'sort_buffer_size';
设置sort_buffer_size的大小
set global sort_buffer_size=大小;
四,Group by优化
explain中的extra会出现一种情况说明group by的效率较低
-
using tempory:代表MYSQL使用了临时表 -
解决办法: 给分组的字段加上索引优化。
五,Limit优化
5.1 基础优化原则
-
避免全表扫描
-- 低效写法(扫描前100010行) SELECT * FROM orders ORDER BY id LIMIT 100000, 10; -- 优化方案:使用覆盖索引 SELECT * FROM orders WHERE id > 100000 ORDER BY id LIMIT 10; -
游标分页(Cursor-based Pagination)
-- 传统分页(性能差) SELECT * FROM products ORDER BY created_at DESC LIMIT 10000, 20; -- 游标分页(记住最后一条的created_at和id) SELECT * FROM products WHERE created_at < '2023-08-01 12:00:00' AND id < 5000 ORDER BY created_at DESC, id DESC LIMIT 20;
5.2 高级优化技巧
-
延迟关联(Deferred Join)
-- 原始查询(全表扫描) SELECT * FROM users WHERE country='CN' ORDER BY score DESC LIMIT 100000, 10; -- 优化版本(先查主键) SELECT users.* FROM users JOIN ( SELECT id FROM users WHERE country='CN' ORDER BY score DESC LIMIT 100000, 10 ) AS tmp USING(id); -
预计算分页数据
-- 创建分页元数据表 CREATE TABLE page_metadata ( page_num INT, start_id INT, end_id INT, PRIMARY KEY (page_num) ); -- 定期预计算(每页100条) REPLACE INTO page_metadata SELECT (ROW_NUMBER() OVER (ORDER BY id)-1)/100 +1 AS page_num, MIN(id) AS start_id, MAX(id) AS end_id FROM orders GROUP BY (ROW_NUMBER() OVER (ORDER BY id)-1)/100; -- 使用元数据快速分页 SELECT * FROM orders WHERE id BETWEEN (SELECT start_id FROM page_metadata WHERE page_num=1000) AND (SELECT end_id FROM page_metadata WHERE page_num=1000);
六,Count优化
在InnoDB存储引擎中他会将表一行行读取并累加,类似与循环++
优化思路: 使用触发器在insert或者delete的时候+1或-1并存储在表中某一字段内即可。
count的几种情况
-
count(*)InnoDB引擎不会把字段全部取出而是专门做了优化,不取值直接在服务层按行进行类型。
-
count(1)我们所查询的每一条记录都会放一个1进去,然后在服务器层对数据进行累加(如果是1就+1)
-
count(主键)InnoDB引擎会遍历整张表,然后把每一行的主键id值取出,返回给服务器层,服务器层拿到后直接开始累加(因为主键不可能为null)
-
count(字段)没有not null约束:innoDB引擎会遍历整张表,然后把每一行的字段值取出返回给服务器层判断是否为null
有not null的约束:InnoDB引擎会遍历整张表,然后把每一行的字段值取出返回给服务器层直接开始累加
从上到下效率依次降低
七,Update优化(避免行锁升级为表锁)
在InnoDB引擎中开启事务后执行update语句他会将数据行锁住即行锁。
事务没提交前行锁不会释放,注意一定要对索引数据进行更新才能避免从行锁升级为表锁。
原因: 在InnoDB引擎中他不是针对记录加的行锁而是根据索引加的行锁,在更新数据的时候如果索引失效那么就会从行锁升级为表锁。