0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

MySQL上亿大表如何深度优化呢

jf_ro2CN3Fa 来源:博客园 作者:jiaxin_12 2022-11-21 11:02 次阅读

背景

XX实例(一主一从)xxx告警中每天凌晨在报SLA报警,该报警的意思是存在一定的主从延迟(若在此时发生主从切换,需要长时间才可以完成切换,要追延迟来保证主从数据的一致性)

XX实例的慢查询数量最多(执行时间超过1s的sql会被记录),XX应用那方每天晚上在做删除一个月前数据的任务

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

项目地址:https://github.com/YunaiV/ruoyi-vue-pro

视频教程:https://doc.iocoder.cn/video/

分析

使用pt-query-digest工具分析最近一周的mysql-slow.log

pt-query-digest--since=148hmysql-slow.log|less

结果第一部分

f93d9626-6940-11ed-8abf-dac502259ad0.png

最近一个星期内,总共记录的慢查询执行花费时间为25403s,最大的慢sql执行时间为266s,平均每个慢sql执行时间5s,平均扫描的行数为1766万

结果第二部分

f9532efa-6940-11ed-8abf-dac502259ad0.png

select arrival_record操作记录的慢查询数量最多有4万多次,平均响应时间为4s,delete arrival_record记录了6次,平均响应时间258s。

select xxx_record语句

select arrival_record 慢查询语句都类似于如下所示,where语句中的参数字段是一样的,传入的参数值不一样select count(*) from arrival_record where product_id=26 and receive_time between '2019-03-25 1400' and '2019-03-25 1500' and receive_spend_ms>=0G

f993113c-6940-11ed-8abf-dac502259ad0.png

select arrival_record 语句在mysql中最多扫描的行数为5600万、平均扫描的行数为172万,推断由于扫描的行数多导致的执行时间长

查看执行计划

explainselectcount(*)fromarrival_recordwhereproduct_id=26andreceive_timebetween'2019-03-251400'and'2019-03-251500'andreceive_spend_ms>=0G;
***************************1.row***************************
id:1
select_type:SIMPLE
table:arrival_record
partitions:NULL
type:ref
possible_keys:IXFK_arrival_record
key:IXFK_arrival_record
key_len:8
ref:const
rows:32261320
filtered:3.70
Extra:Usingindexcondition;Usingwhere
1rowinset,1warning(0.00sec)

用到了索引IXFK_arrival_record,但预计扫描的行数很多有3000多w行

showindexfromarrival_record;
+----------------+------------+---------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
|Table|Non_unique|Key_name|Seq_in_index|Column_name|Collation|Cardinality|Sub_part|Packed|Null|Index_type|Comment|Index_comment|
+----------------+------------+---------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
|arrival_record|0|PRIMARY|1|id|A|107990720|NULL|NULL||BTREE|||
|arrival_record|1|IXFK_arrival_record|1|product_id|A|1344|NULL|NULL||BTREE|||
|arrival_record|1|IXFK_arrival_record|2|station_no|A|22161|NULL|NULL|YES|BTREE|||
|arrival_record|1|IXFK_arrival_record|3|sequence|A|77233384|NULL|NULL||BTREE|||
|arrival_record|1|IXFK_arrival_record|4|receive_time|A|65854652|NULL|NULL|YES|BTREE|||
|arrival_record|1|IXFK_arrival_record|5|arrival_time|A|73861904|NULL|NULL|YES|BTREE|||
+----------------+------------+---------------------+--------------+--------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
showcreatetablearrival_record;
..........
arrival_spend_msbigint(20)DEFAULTNULL,
total_spend_msbigint(20)DEFAULTNULL,
PRIMARYKEY(id),
KEYIXFK_arrival_record(product_id,station_no,sequence,receive_time,arrival_time)USINGBTREE,
CONSTRAINTFK_arrival_record_productFOREIGNKEY(product_id)REFERENCESproduct(id)ONDELETENOACTIONONUPDATENOACTION
)ENGINE=InnoDBAUTO_INCREMENT=614538979DEFAULTCHARSET=utf8COLLATE=utf8_bin|

该表总记录数约1亿多条,表上只有一个复合索引,product_id字段基数很小,选择性不好

传入的过滤条件 where product_id=26 and receive_time between '2019-03-25 1400' and '2019-03-25 1500' and receive_spend_ms>=0 没有station_nu字段,使用不到复合索引 IXFK_arrival_record的 product_id,station_no,sequence,receive_time 这几个字段

根据最左前缀原则,select arrival_record只用到了复合索引IXFK_arrival_record的第一个字段product_id,而该字段选择性很差,导致扫描的行数很多,执行时间长

receive_time字段的基数大,选择性好,可对该字段单独建立索引,select arrival_record sql就会使用到该索引

现在已经知道了在慢查询中记录的select arrival_record where语句传入的参数字段有 product_id,receive_time,receive_spend_ms,还想知道对该表的访问有没有通过其它字段来过滤了?

神器tcpdump出场的时候到了

使用tcpdump抓包一段时间对该表的select语句

tcpdump-ibond0-s0-l-w-dstport3316|strings|grepselect|egrep-i'arrival_record'>/tmp/select_arri.log

获取select 语句中from 后面的where条件语句

IFS_OLD=$IFS
IFS=$'
'
foriin`cat/tmp/select_arri.log`;doecho${i#*'from'};done|less
IFS=$IFS_OLD
arrival_recordarrivalrec0_wherearrivalrec0_.sequence='2019-03-2708:40'andarrivalrec0_.product_id=17andarrivalrec0_.station_no='56742'
arrival_recordarrivalrec0_wherearrivalrec0_.sequence='2019-03-2708:40'andarrivalrec0_.product_id=22andarrivalrec0_.station_no='S7100'
arrival_recordarrivalrec0_wherearrivalrec0_.sequence='2019-03-2708:40'andarrivalrec0_.product_id=24andarrivalrec0_.station_no='V4631'
arrival_recordarrivalrec0_wherearrivalrec0_.sequence='2019-03-2708:40'andarrivalrec0_.product_id=22andarrivalrec0_.station_no='S9466'
arrival_recordarrivalrec0_wherearrivalrec0_.sequence='2019-03-2708:40'andarrivalrec0_.product_id=24andarrivalrec0_.station_no='V4205'
arrival_recordarrivalrec0_wherearrivalrec0_.sequence='2019-03-2708:40'andarrivalrec0_.product_id=24andarrivalrec0_.station_no='V4105'
arrival_recordarrivalrec0_wherearrivalrec0_.sequence='2019-03-2708:40'andarrivalrec0_.product_id=24andarrivalrec0_.station_no='V4506'
arrival_recordarrivalrec0_wherearrivalrec0_.sequence='2019-03-2708:40'andarrivalrec0_.product_id=24andarrivalrec0_.station_no='V4617'
arrival_recordarrivalrec0_wherearrivalrec0_.sequence='2019-03-2708:40'andarrivalrec0_.product_id=22andarrivalrec0_.station_no='S8356'
arrival_recordarrivalrec0_wherearrivalrec0_.sequence='2019-03-2708:40'andarrivalrec0_.product_id=22andarrivalrec0_.station_no='S8356'
select该表where条件中有product_id,station_no,sequence字段,可以使用到复合索引IXFK_arrival_record的前三个字段

综上所示,优化方法为,删除复合索引IXFK_arrival_record,建立复合索引idx_sequence_station_no_product_id,并建立单独索引indx_receive_time

delete xxx_record语句

f9b6107e-6940-11ed-8abf-dac502259ad0.png

该delete操作平均扫描行数为1.1亿行,平均执行时间是262s

delete语句如下所示,每次记录的慢查询传入的参数值不一样

deletefromarrival_recordwherereceive_time< STR_TO_DATE('2019-02-23', '%Y-%m-%d')G

执行计划

explainselect*fromarrival_recordwherereceive_time< STR_TO_DATE('2019-02-23', '%Y-%m-%d')G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: arrival_record
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 109501508
filtered: 33.33
Extra: Using where
1 row in set, 1 warning (0.00 sec)

该delete语句没有使用索引(没有合适的索引可用),走的全表扫描,导致执行时间长

优化方法也是 建立单独索引indx_receive_time(receive_time)

基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能

项目地址:https://github.com/YunaiV/yudao-cloud

视频教程:https://doc.iocoder.cn/video/

测试

拷贝arrival_record表到测试实例上进行删除重新索引操作XX实例arrival_record表信息

du-sh/datas/mysql/data/3316/cq_new_cimiss/arrival_record*
12K/datas/mysql/data/3316/cq_new_cimiss/arrival_record.frm
48G/datas/mysql/data/3316/cq_new_cimiss/arrival_record.ibd
selectcount()fromcq_new_cimiss.arrival_record;
+-----------+
|count()|
+-----------+
|112294946|
+-----------+
1亿多记录数

SELECT
table_name,
CONCAT(FORMAT(SUM(data_length)/1024/1024,2),'M')ASdbdata_size,
CONCAT(FORMAT(SUM(index_length)/1024/1024,2),'M')ASdbindex_size,
CONCAT(FORMAT(SUM(data_length+index_length)/1024/1024/1024,2),'G')AStable_size(G),
AVG_ROW_LENGTH,table_rows,update_time
FROM
information_schema.tables
WHEREtable_schema='cq_new_cimiss'andtable_name='arrival_record';
+----------------+-------------+--------------+------------+----------------+------------+---------------------+
|table_name|dbdata_size|dbindex_size|table_size(G)|AVG_ROW_LENGTH|table_rows|update_time|
+----------------+-------------+--------------+------------+----------------+------------+---------------------+
|arrival_record|18,268.02M|13,868.05M|31.38G|175|109155053|2019-03-2612:40:17|
+----------------+-------------+--------------+------------+----------------+------------+---------------------+

磁盘占用空间48G,mysql中该表大小为31G,存在17G左右的碎片,大多由于删除操作造成的(记录被删除了,空间没有回收)

备份还原该表到新的实例中,删除原来的复合索引,重新添加索引进行测试

mydumper并行压缩备份

user=root
passwd=xxxx
socket=/datas/mysql/data/3316/mysqld.sock
db=cq_new_cimiss
table_name=arrival_record
backupdir=/datas/dump_$table_name
mkdir-p$backupdir
nohupecho`date+%T`&&mydumper-u$user-p$passwd-S$socket-B$db-c-T$table_name-o$backupdir-t32-r2000000&&echo`date+%T`&

并行压缩备份所花时间(52s)和占用空间(1.2G,实际该表占用磁盘空间为48G,mydumper并行压缩备份压缩比相当高!)

Starteddumpat:2019-03-2612:46:04
........

Finisheddumpat:2019-03-2612:46:56

du-sh/datas/dump_arrival_record/
1.2G/datas/dump_arrival_record/

拷贝dump数据到测试节点

scp-rp/datas/dump_arrival_recordroot@10.230.124.19:/datas

多线程导入数据

timemyloader-uroot-S/datas/mysql/data/3308/mysqld.sock-P3308-proot-Btest-d/datas/dump_arrival_record-t32

real 126m42.885suser 1m4.543ssys 0m4.267s

逻辑导入该表后磁盘占用空间

du-h-d1/datas/mysql/data/3308/test/arrival_record.*
12K/datas/mysql/data/3308/test/arrival_record.frm
30G/datas/mysql/data/3308/test/arrival_record.ibd
没有碎片,和mysql的该表的大小一致
cp-rp/datas/mysql/data/3308/datas

分别使用online DDL和 pt-osc工具来做删除重建索引操作先删除外键,不删除外键,无法删除复合索引,外键列属于复合索引中第一列

nohupbash/tmp/ddl_index.sh&
2019-04-04-10:41:39beginstopmysqld_3308
2019-04-04-10:41:41beginrm-rfdatadirandcp-rpdatadir_bak
2019-04-04-10:46:53startmysqld_3308
2019-04-04-10:46:59onlineddlbegin
2019-04-04-11:20:34onlieddlstop
2019-04-04-11:20:34beginstopmysqld_3308
2019-04-04-11:20:36beginrm-rfdatadirandcp-rpdatadir_bak
2019-04-04-11:22:48startmysqld_3308
2019-04-04-11:22:53pt-oscbegin
2019-04-04-12:19:15pt-oscstop
onlineddl花费时间为34分钟,pt-osc花费时间为57分钟,使用onlneddl时间约为pt-osc工具时间的一半

*做DDL 参考 *

f9cde8de-6940-11ed-8abf-dac502259ad0.png

实施

由于是一主一从实例,应用是连接的vip,删除重建索引采用online ddl来做。停止主从复制后,先在从实例上做(不记录binlog),主从切换,再在新切换的从实例上做(不记录binlog)

functionred_echo(){

localwhat="$*"
echo-e"$(date+%F-%T)${what}"
}

functioncheck_las_comm(){
if["$1"!="0"];then
red_echo"$2"
echo"exit1"
exit1
fi
}

red_echo"stopslave"
mysql-uroot-p$passwd--socket=/datas/mysql/data/${port}/mysqld.sock-e"stopslave"
check_las_comm"$?""stopslavefailed"

red_echo"onlineddlbegin"
mysql-uroot-p$passwd--socket=/datas/mysql/data/${port}/mysqld.sock-e"setsql_log_bin=0;selectnow()asddl_start;ALTERTABLE$db_.`${table_name}`DROPFOREIGNKEYFK_arrival_record_product,dropindexIXFK_arrival_record,addindexidx_product_id_sequence_station_no(product_id,sequence,station_no),addindexidx_receive_time(receive_time);selectnow()asddl_stop">>${log_file}2>&1
red_echo"onlieddlstop"
red_echo"addforeignkey"
mysql-uroot-p$passwd--socket=/datas/mysql/data/${port}/mysqld.sock-e"setsql_log_bin=0;ALTERTABLE$db_.${table_name}ADDCONSTRAINT_FK_${table_name}_productFOREIGNKEY(product_id)REFERENCEScq_new_cimiss.product(id)ONDELETENOACTIONONUPDATENOACTION;">>${log_file}2>&1
check_las_comm"$?""addforeignkeyerror"
red_echo"addforeignkeystop"

red_echo"startslave"
mysql-uroot-p$passwd--socket=/datas/mysql/data/${port}/mysqld.sock-e"startslave"
check_las_comm"$?""startslavefailed"

*执行时间 *

2019-04-08-1136 stop slavemysql: [Warning] Using a password on the command line interface can be insecure.ddl_start2019-04-08 1136ddl_stop2019-04-08 11132019-04-08-1113 onlie ddl stop2019-04-08-1113 add foreign keymysql: [Warning] Using a password on the command line interface can be insecure.2019-04-08-1248 add foreign key stop2019-04-08-1248 start slave

*再次查看delete 和select语句的执行计划 *

explainselectcount(*)fromarrival_recordwherereceive_time< STR_TO_DATE('2019-03-10', '%Y-%m-%d')G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: arrival_record
partitions: NULL
type: range
possible_keys: idx_receive_time
key: idx_receive_time
key_len: 6
ref: NULL
rows: 7540948
filtered: 100.00
Extra: Using where; Using index
explain select count(*) from arrival_record where product_id=26 and receive_time between '2019-03-25 1400' and '2019-03-25 1500' and receive_spend_ms>=0G;
***************************1.row***************************
id:1
select_type:SIMPLE
table:arrival_record
partitions:NULL
type:range
possible_keys:idx_product_id_sequence_station_no,idx_receive_time
key:idx_receive_time
key_len:6
ref:NULL
rows:291448
filtered:16.66
Extra:Usingindexcondition;Usingwhere
都使用到了idx_receive_time索引,扫描的行数大大降低

索引优化后

delete 还是花费了77s时间

deletefromarrival_recordwherereceive_time< STR_TO_DATE('2019-03-10', '%Y-%m-%d')G
f9effe74-6940-11ed-8abf-dac502259ad0.png

delete 语句通过receive_time的索引删除300多万的记录花费77s时间*

delete大表优化为小批量删除

*应用端已优化成每次删除10分钟的数据(每次执行时间1s左右),xxx中没在出现SLA(主从延迟告警) *

fa29ffb6-6940-11ed-8abf-dac502259ad0.png

*另一个方法是通过主键的顺序每次删除20000条记录 *

#得到满足时间条件的最大主键ID
#通过按照主键的顺序去顺序扫描小批量删除数据
#先执行一次以下语句
SELECTMAX(id)INTO@need_delete_max_idFROM`arrival_record`WHEREreceive_time<'2019-03-01' ;
 DELETE FROM arrival_record WHERE id<@need_delete_max_id LIMIT 20000;
 select ROW_COUNT();  #返回20000


#执行小批量delete后会返回row_count(), 删除的行数
#程序判断返回的row_count()是否为0,不为0执行以下循环,为0退出循环,删除操作完成
 DELETE FROM arrival_record WHERE id<@need_delete_max_id LIMIT 20000;
 select ROW_COUNT();
#程序睡眠0.5s

总结

表数据量太大时,除了关注访问该表的响应时间外,还要关注对该表的维护成本(如做DDL表更时间太长,delete历史数据)。

对大表进行DDL操作时,要考虑表的实际情况(如对该表的并发表,是否有外键)来选择合适的DDL变更方式。

对大数据量表进行delete,用小批量删除的方式,减少对主实例的压力和主从延迟。





审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • SQL
    SQL
    +关注

    关注

    1

    文章

    760

    浏览量

    44080
  • DDL
    DDL
    +关注

    关注

    0

    文章

    12

    浏览量

    6320
  • MYSQL数据库
    +关注

    关注

    0

    文章

    95

    浏览量

    9382

原文标题:面试官:MySQL 上亿大表,如何深度优化?

文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    MySQL的执行过程 SQL语句性能优化常用策略

    回顾 MySQL 的执行过程,帮助介绍如何进行 sql 优化
    的头像 发表于 12-12 10:26 627次阅读
    <b class='flag-5'>MySQL</b>的执行过程 SQL语句性能<b class='flag-5'>优化</b>常用策略

    mysql中文参考手册chm

    数据库类型 10 从 MySQL 得到最大的性能 10.1 优化概述 10.2 系统/编译时和启动参数的调节 10.2.1 编译和链接如何影响 MySQL 的速度 10.2.2 磁盘
    发表于 12-26 13:32

    MySQL分区类型及介绍

    分区是将一个的数据按照一定规则水平划分成不同的逻辑块,并分别进行物理存储,这个规则就叫做分区函数,可以有不同的分区规则。通过show plugins语句查看当前MySQL是否支持
    发表于 06-29 16:31

    Mysql优化选择最佳索引规则

    索引的目的在于提高查询效率,其功能可类比字典,通过该索引可以查询到我们想要查询的信息,因此,选择建立好的索引十分重要,以下是为Mysql优化选择最佳索引的方法步骤:1. 首先列出查询中所有使用的
    发表于 07-06 15:13

    关于MySQL分区和分的详细介绍

    MySQL分区和分
    发表于 07-10 07:40

    mysql数据库优化方案

    MySQL千万级大优化解决方案
    发表于 08-19 12:18

    mysql的查询优化

    mysql查询优化
    发表于 03-12 11:06

    mysql的结构修改、约束

    mysql结构修改、约束(二)
    发表于 05-21 10:26

    MySQL优化之查询性能优化之查询优化器的局限性与提示

    MySQL优化三:查询性能优化之查询优化器的局限性与提示
    发表于 06-02 06:34

    MySQL索引使用优化和规范

    MySQL - 索引使用优化和规范
    发表于 06-15 16:01

    MySql5.6性能优化最佳实践

    MySql5.6性能优化最佳实践
    发表于 09-08 08:47 13次下载
    <b class='flag-5'>MySql</b>5.6性能<b class='flag-5'>优化</b>最佳实践

    MySQL数据库:理解MySQL的性能优化优化查询

    最近一直在为大家更新MySQL相关学习内容,可能有朋友不懂MySQL的重要性。在程序,语言,架构更新换代频繁的今天,MySQL 恐怕是大家使用最多的存储数据库了。由于MySQL
    的头像 发表于 07-02 17:18 3069次阅读
    <b class='flag-5'>MySQL</b>数据库:理解<b class='flag-5'>MySQL</b>的性能<b class='flag-5'>优化</b>、<b class='flag-5'>优化</b>查询

    你会从哪些维度进行MySQL性能优化?1

    你会从哪些维度进行MySQL性能优化?你会怎么回答? 所谓的性能优化,一般针对的是MySQL查询的优化。既然是
    的头像 发表于 03-03 10:23 487次阅读
    你会从哪些维度进行<b class='flag-5'>MySQL</b>性能<b class='flag-5'>优化</b>?1

    你会从哪些维度进行MySQL性能优化?2

    你会从哪些维度进行MySQL性能优化?你会怎么回答? 所谓的性能优化,一般针对的是MySQL查询的优化。既然是
    的头像 发表于 03-03 10:23 486次阅读
    你会从哪些维度进行<b class='flag-5'>MySQL</b>性能<b class='flag-5'>优化</b>?2

    MySQL性能优化方法

    MySQL 性能优化是一项关键的任务,可以提高数据库的运行速度和效率。以下是一些优化方法,包括具体代码和详细优化方案。
    的头像 发表于 11-22 09:59 563次阅读