一文道尽数据库底层原理,探讨Mysql调优之道
Posted 小飞龙编程
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文道尽数据库底层原理,探讨Mysql调优之道相关的知识,希望对你有一定的参考价值。
前段时间看过一部电影,叫做《英雄时代》,里面有句话是这样说的:“生活这条狗啊,追的我连从容撒泡尿的时间都没有。”
在这个聪明人满街乱窜的年代,稀缺的恰恰不是聪明,而是一心一意,孤注一掷,一条心,一根筋。
人生最可怕的是,一生碌碌无为,还安慰自己平凡可贵。
数据库调优
慢查询日志
慢查询日志是mysql内置的一项功能,可以记录执行超过指定时间的Sql语句
考虑到慢查询日志细节比较多,在这里我专门记录了一部分手记,一起来看一下:
相关参数与默认值
参数 | 作用 | 默认值 |
---|---|---|
log_output | 日志输出到哪,默认FILE,表示文件;设置成TABLE,则将日志记录到mysql.slow_log中。也可能设置多种格式,比如:FILE,TABLE | FILE |
long_query_time | 执行时间超过这么久才记录到慢查询日志,单位秒,可使用小数表示小于秒的时间 | 10 |
log_queries_not_using_indexes | 是否要将未使用索引的SQL记录到慢查询日志中,此配置会无视log_query_time的配置。生产环境建议关闭;开发环境建议开启。 | OFF |
log_throttle_queries_not_using_indexes | 和log_queries_not_using_indexes配合使用,如果log_queries_not_using_indexes打开,则该参数将限制每分钟写入的、未使用索引的SQL数量。 | 0 |
min_examined_row_limit | 扫描行数至少达到这么多才记录的慢查询日志 | 0 |
log_slow_admin_statements | 是否要记录管理语句,默认关闭。管理语句包括ALERT TABLE,ANALYZE TABLE,CHECK TABLE,CREATE INDEX,DROP INDEX,OPTIMIZE TABLE,and REPAIR TABLE | OFF |
slow_query_log_file | 指定慢查询日志文件路径 | /var路径 |
log_slow_slave_statements | 该参数在从库上设置,决定是否记录在复制过程中超过long_query_time的SQL。如果binlog格式是row,则该参数无效 | OFF |
log_slow_extra | 当log_output=FILE时,是否要记录额外信息(MySql 8.0.14开始提供),对log_output=TABLE的结果无影响。 | OFF |
使用方式
方式一、修改配置文件my.cnf,在[mysqld]段落中加上如下参数即可
例如:
[mysqld]
#...
log_output = 'FILE,TABLE'
slow_query_log = ON
long_query_time = 0.001
然后重启mysql
service mysqld restart
方式二、通过全局变量设置
这种方式无需重启即可生效,但一旦重启,配置又会失效。
例如:
set global log_output = 'FILE,TABLE';
set global slow_query_log = 'NO';
set global long_query_time = 0.001;
这样设置之后,就会将慢查询日志同时记录到文件以及mysql.slow_log表中。
分析慢查询日志
分析慢查询日志表
当log_output = TABLE时,可直接用如下语句分析:
select * from `mysql`.slow_log
然后按照条件做各种查询、统计、分析。
分析慢查询日志文件
mysqldumpslow
当log_output = FILE时,可使用mysqldumpslow分析
[server@server-test ~]$ mysqldumpslow --help
Usage: mysqldumpslow [ OPTS... ] [ LOGS... ]
Parse and summarize the MySQL slow query log. Options are
--verbose verbose
--debug debug
--help write this text to standard output
-v 展示更详细的信息
-d debug
-s ORDER 以哪种方式排序 (al, at, ar, c, l, r, t), 默认 'at'
al: 平均锁定时间
ar: 平均返回记录数
at: 平均查询时间
c: 访问计数
l: 锁定时间
r: 返回记录
t: 查询时间
-r 将-s的排序倒序
-t NUM top n的意思,展示最前面的几条
-a 不去将数字展示成N,将字符串展示成'S'
-n NUM abstract numbers with at least n digits within names
-g PATTERN 后边可以写一个正则,只有符合正则的行会展示
-h HOSTNAME 慢查询日志以 主机名 =slow.log的格式命名,-h可指定读取指定主机名的慢查询日志,默认情况下是*,读取所有的慢查询日志
-i NAME Mysql Server的实例名称(如果使用了mysql.server startup脚本的话)
-l 不将锁定时间从总时间减去
pt-query-digest
我们也可以用pt-query-digest分析慢查询日志文件。pt-query-digest是Percona公司开发的工具,是Percona Toolkit工具套件的使用工具之一。这里就不详细探究了,感兴趣的同学自行研究吧。
EXPLAIN详解
explain可以帮助我们分析mysql的执行计划,
EXPLAIN使用
explain可用来分析SQL的执行计划。
我们来演示一下:
这样我们就可以知道
SELECT * from user_info where nickname = 'Ant'
这条Sql语句是怎样执行的了。
结果输出展示:
字段 | format=json时的名称 | 含义 |
---|---|---|
id | select_id | 该语句的唯一标识 |
select_type | 无 | 查询类型 |
table | table_name | 表名 |
partitions | partitions | 匹配的分区 |
type | access_type | 连接类型 |
possible_keys | possible_keys | 可能的索引选择 |
key | key | 实际选择的索引 |
key_len | key_length | 索引的长度 |
ref | ref | 索引的哪一列被引用了 |
rows | rows | 估计要扫描的行 |
filtered | filtered | 表示符合查询条件的数据百分比 |
Extra | 没有 | 附加信息 |
接下来我们分析一下执行的结果:
首先,这里的select_type是SIMPLE 表示这是一个简单查询,table表示查询的表是user_info ,type是ALL表示发生了全表扫描,possible_keys、key、key_len都是空,表示没有使用任何索引,rows表示执行这个sql需要扫描25万多数据才能返回,filtered是10%,最后Extra是Using where表示使用了where条件。
根据这个分析来看,这条sql的性能是比较差的。
我们执行一下看看:
看到花费了800多毫秒,性能果然不太OK。
我们再来看下面一段演示:
从这里我们可以发现,explain执行后展示了两行结果,当有多条结果的时候这个id字段是有用的,它可以描述sql的执行过程。如果一个explain的执行结果包含多个id值,比如id=1以及id=2那么数字越大越先执行;而对于相同id的行,比如上图中id都是1,那么会从上到下依次执行。
SQL性能分析
一般来说,使用Explain已经满足大多数场景下分析SQL的需求,但是如果想更加细致的分析SQL的话,Explain可能还是不够的,那我们就来探讨一下,如何深入到Sql内部,去了解一条Sql到底执行了哪些步骤,每个步骤花费了多久,性能瓶颈出现在了哪个步骤。
如何深入SQL内部,去分析其性能,包括了三种方式:
- SHOW PROFILE
- INFORMATION_SCHEMA.PROFILING
- PERFORMANCE_SCHEMA
SHOW PROFILE
SHOW PROFILE是MySQL的一个性能分析指令,可以跟踪SQL各种资源消耗。使用格式如下:
SHOW PROFILE [type [, type] ... ]
[FOR QUERY n]
[LIMIT row_count [OFFSET offset]]
type: {
ALL 显示所有信息
| BLOCK IO 显示阻塞的输入输出次数
| CONTEXT SWITCHES 显示自愿及非自愿的上下文切换次数
| CPU 显示用户与系统CPU使用时间
| IPC 显示消息发送与接收的次数
| MEMORY 显示内存相关的开销,目前未实现此功能
| PAGE FAULTS 显示页错误相关开销信息
| SOURCE 列出相应操作对应的函数名及其在源码中的位置(行)
| SWAPS 显示swap交换次数
}
默认情况下,SHOW PROFILE只展示Status和Duration两列,如果想展示更多信息,可指定type。
-
使用如下命令,查看是否支持SHOW PROFILE功能,yes标志支持。从MySQL 5.0.37开始,MySQL支持SHOW PROFILE
select @@have_profiling;
-
查看当前是否启用了SHOW PROFILE,0表示未启用,1表示已启用
select @@profiling;
-
使用如下命令为当前会话开启或关闭性能分析,设成1表示开启,0表示关闭
set profiling = 1;
-
使用SHOW PROFILES命令,可为最近发送的SQL语句做一个概要的性能分析。展示的条目数目由profiling_history_size会话变量控制,该变量的默认值为15。最大值为100。将值设置为0具有禁用分析的实际效果。
-
– 默认展示15条
show profiles -- 使用profiling_history_size调整展示的条目数 set profiling_history_size = 100;
-
使用show profile分析指定查询:
mysql> SHOW PROFILES; +----------+----------+--------------------------+ | Query_ID | Duration | Query | +----------+----------+--------------------------+ | 0 | 0.000088 | SET PROFILING = 1 | | 1 | 0.000136 | DROP TABLE IF EXISTS t1 | | 2 | 0.011947 | CREATE TABLE t1 (id INT) | +----------+----------+--------------------------+ 3 rows in set (0.00 sec) mysql> SHOW PROFILE; +----------------------+----------+ | Status | Duration | +----------------------+----------+ | checking permissions | 0.000040 | | creating table | 0.000056 | | After create | 0.011363 | | query end | 0.000375 | | freeing items | 0.000089 | | logging slow query | 0.000019 | | cleaning up | 0.000005 | +----------------------+----------+ 7 rows in set (0.00 sec) -- 默认情况下,只展示Status和Duration两列,如果想展示更多信息,可指定type。 mysql> SHOW PROFILE FOR QUERY 1; +--------------------+----------+ | Status | Duration | +--------------------+----------+ | query end | 0.000107 | | freeing items | 0.000008 | | logging slow query | 0.000015 | | cleaning up | 0.000006 | +--------------------+----------+ 4 rows in set (0.00 sec) -- 展示CPU相关的开销 mysql> SHOW PROFILE CPU FOR QUERY 2; +----------------------+----------+----------+------------+ | Status | Duration | CPU_user | CPU_system | +----------------------+----------+----------+------------+ | checking permissions | 0.000040 | 0.000038 | 0.000002 | | creating table | 0.000056 | 0.000028 | 0.000028 | | After create | 0.011363 | 0.000217 | 0.001571 | | query end | 0.000375 | 0.000013 | 0.000028 | | freeing items | 0.000089 | 0.000010 | 0.000014 | | logging slow query | 0.000019 | 0.000009 | 0.000010 | | cleaning up | 0.000005 | 0.000003 | 0.000002 | +----------------------+----------+----------+------------+ 7 rows in set (0.00 sec) ```
-
分析完成后,记得关闭掉SHOW PROFILE功能:
set profiling = 0;
TIPS
- MySQL官方文档声明SHOW PROFILE已被废弃,并建议使用Performance Schema作为替代品。
- 在某些系统上,性能分析只有部分功能可用。比如,部分功能在Windows系统下无效(show profile使用了getrusage()这个API,而在Windows上将会返回false,因为Windows不支持这个API);此外,性能分析是进程级的,而不是线程级的,这意味着其他线程的活动可能会影响到你看到的计时信息。
INFORMATION_SCHEMA.PROFILING
INFORMATION_SCHEMA.PROFILING用来做性能分析。它的内容对应SHOW PROFILE和SHOW PROFILES 语句产生的信息。除非设置了 set profiling = 1; ,否则该表不会有任何数据。该表包括以下字段:
- QUERY_ID:语句的唯一标识
- SEQ:一个序号,展示具有相同QUERY_ID值的行的显示顺序
- STATE:分析状态
- DURATION:在这个状态下持续了多久(秒)
- CPU_USER,CPU_SYSTEM:用户和系统CPU使用情况(秒)
- CONTEXT_VOLUNTARY,CONTEXT_INVOLUNTARY:发生了多少自愿和非自愿的上下文转换
- BLOCK_OPS_IN,BLOCK_OPS_OUT:块输入和输出操作的数量
- MESSAGES_SENT,MESSAGES_RECEIVED:发送和接收的消息数
- PAGE_FAULTS_MAJOR,PAGE_FAULTS_MINOR:主要和次要的页错误信息
- SWAPS:发生了多少SWAP
- SOURCE_FUNCTION,SOURCE_FILE,SOURCE_LINE:当前状态是在源码的哪里执行的
TIPS
- SHOW PROFILE本质上使用的也是INFORMATION_SCHEMA.PROFILING
- INFORMATION_SCHEMA.PROFILING表已被废弃,在未来可能会被删除。未来将可使用Performance Schema替代,详见 “Query Profiling Using Performance Schema”
- 下面两个SQL是等价的:
SHOW PROFILE FOR QUERY 2; SELECT STATE, FORMAT(DURATION, 6) AS DURATION FROM INFORMATION_SCHEMA.PROFILING WHERE QUERY_ID = 2 ORDER BY SEQ;
PERFORMANCE_SCHEMA
PERFORMANCE_SCHEMA是MySQL建议的性能分析方式,未来SHOW PROFILE、INFORMATION_SCHEMA.PROFILING都会废弃。据笔者研究,PERFORMANCE_SCHEMA在MySQL 5.6引入,因此,在MySQL 5.6及更高版本才能使用。可使用SHOW VARIABLES LIKE 'performance_schema';
查看启用情况,MySQL 5.7开始默认启用。
下面来用PERFORMANCE_SCHEMA去实现SHOW PROFILE类似的效果:
-
查看是否开启性能监控
mysql> SELECT * FROM performance_schema.setup_actors; +------+------+------+---------+---------+ | HOST | USER | ROLE | ENABLED | HISTORY | +------+------+------+---------+---------+ | % | % | % | YES | YES | +------+------+------+---------+---------+
默认是开启的。
-
你也可以执行类似如下的SQL语句,只监控指定用户执行的SQL:
mysql> UPDATE performance_schema.setup_actors SET ENABLED = 'NO', HISTORY = 'NO' WHERE HOST = '%' AND USER = '%'; mysql> INSERT INTO performance_schema.setup_actors (HOST,USER,ROLE,ENABLED,HISTORY) VALUES('localhost','test_user','%','YES','YES');
这样,就只会监控localhost机器上test_user用户发送过来的SQL。其他主机、其他用户发过来的SQL统统不监控。
-
执行如下SQL语句,开启相关监控项:
mysql> UPDATE performance_schema.setup_instruments SET ENABLED = 'YES', TIMED = 'YES' WHERE NAME LIKE '%statement/%'; mysql> UPDATE performance_schema.setup_instruments SET ENABLED = 'YES', TIMED = 'YES' WHERE NAME LIKE '%stage/%'; mysql> UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME LIKE '%events_statements_%'; mysql> UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME LIKE '%events_stages_%';
-
使用开启监控的用户,执行SQL语句,比如:
mysql> SELECT * FROM employees.employees WHERE emp_no = 10001; +--------+------------+------------+-----------+--------+------------+ | emp_no | birth_date | first_name | last_name | gender | hire_date | +--------+------------+------------+-----------+--------+------------+ | 10001 | 1953-09-02 | Georgi | Facello | M | 1986-06-26 | +--------+------------+------------+-----------+--------+------------+
-
执行如下SQL,获得语句的EVENT_ID。
mysql> SELECT EVENT_ID, TRUNCATE(TIMER_WAIT/1000000000000,6) as Duration, SQL_TEXT FROM performance_schema.events_statements_history_long WHERE SQL_TEXT like '%10001%'; +----------+----------+--------------------------------------------------------+ | event_id | duration | sql_text | +----------+----------+--------------------------------------------------------+ | 31 | 0.028310 | SELECT * FROM employees.employees WHERE emp_no = 10001 | +----------+----------+--------------------------------------------------------+
这一步类似于 SHOW PROFILES。
-
执行如下SQL语句做性能分析,这样就可以知道这条语句各种阶段的信息了。
mysql> SELECT event_name AS Stage, TRUNCATE(TIMER_WAIT/1000000000000,6) AS Duration FROM performance_schema.events_stages_history_long WHERE NESTING_EVENT_ID=31; +--------------------------------+----------+ | Stage | Duration | +--------------------------------+----------+ | stage/sql/starting | 0.000080 | | stage/sql/checking permissions | 0.000005 | | stage/sql/Opening tables | 0.027759 | | stage/sql/init | 0.000052 | | stage/sql/System lock | 0.000009 | | stage/sql/optimizing | 0.000006 | | stage/sql/statistics | 0.000082 | | stage/sql/preparing | 0.000008 | | stage/sql/executing | 0.000000 | | stage/sql/Sending data | 0.000017 | | stage/sql/end | 0.000001 | | stage/sql/query end | 0.000004 | | stage/sql/closing tables | 0.000006 | | stage/sql/freeing items | 0.000272 | | stage/sql/cleaning up | 0.000001 | +--------------------------------+----------+
OPTIMIZER_TRACE
我们再来聊一下分析SQL的另一款神器:OPTIMIZER_TRACE
翻译成中文叫做“优化器跟踪”。
它可以跟踪优化器做出各种决策,了解优化器的执行细节,进而帮助我们理解SQL的执行过程,并且优化SQL。
OPTIMIZER_TRACE是MySQL5.6引入的一项功能,此功能默认是关闭的,开启后,可分析如下语句:
- SELECT
- INSERT
- REPLACE
- UPDATE
- DELETE
- EXPLAIN
- SET
- DECLARE
- CASE
- IF
- RETURN
- CALL
OPTIMIZER_TRACE相关参数
参考 https://dev.mysql.com/doc/internals/en/system-variables-controlling-trace.html
- –optimizer-trace
- –optimizer-trace:总开关,默认值:
enabled=off,one_line=off
- enabled:是否开启optimizer_trace;on表示开启,off表示关闭。
- one_line:是否开启单行存储。on表示开启;off表示关闭,将会用标准的JSON格式化存储。设置成on将会有良好的格式,设置成off可节省一些空间。
- –optimizer-trace:总开关,默认值:
- optimizer_trace_features
- 控制optimizer_trace跟踪的内容,默认值:
greedy_search=on,range_optimizer=on,dynamic_range=on,repeated_subselect=on
,表示开启所有跟踪项。 - greedy_search:是否跟踪贪心搜索,有关贪心算法详见:https://blog.csdn.net/qq_37763204/article/details/79289532
- range_optimizer:是否跟踪范围优化器
- dynamic_range:是否跟踪动态范围优化
- repeated_subselect:是否跟踪子查询,如果设置成off,只跟踪第一条Item_subselect的执行
- 控制optimizer_trace跟踪的内容,默认值:
- optimizer_trace_limit:控制optimizer_trace展示多少条结果,默认1
- optimizer_trace_max_mem_size:optimizer_trace堆栈信息允许的最大内存,默认1048586
- optimizer_trace_offset:第一个要展示的optimizer trace的偏移量,默认-1。
- end_markers_in_json:如果JSON结构很大,则很难将右括号和左括号配对。为了帮助读者阅读,可将其设置成on,这样会在右括号附近加上注释,默认off。
以上参数可以用作SET语句操作,例如,用如下命令即可打开OPTIMIZER TRACE
SET OPTIMIZER_TRACE="enabled=on",END_MARKERS_IN_JSON=on;
也可以用SET GLOBAL全局开启,但即使全局开启,每个Session也只能跟踪它自己执行的语句:
SET GLOBAL OPTIMIZER_TRACE="enabled=on",END_MARKERS_IN_JSON=on
optimizer_trace_limit和optimizer_trace_offset这两个参数经常配合使用,例如:
SET optimizer_trace_offset=<OFFSET>,optimizer_trace_limit=<LIMIT>
这两个参数配合使用,有点类似MySQL里面的limit语句。
默认情况下,由于optimizer_trace_offset=-1,optimizer_trace_limit=1,记录最近的一条SQL语句,展示时,每次展示一条数据;
OPTIMIZER_TRACE使用
- 开启OPTIMIZER_TRACE功能,并设置要展示的数据条目数:
SET OPTIMIZER_TRACE="enabled=on",END_MARKERS_IN_JSON=on; SET optimizer_trace_offset=-30,optimizer_trace_limit=30;
- 发送你想要分析的查询语句,例如:
select * from user_info where nickname = 'Ant' and ctime > '2021-02-01'
- 使用如下语句分析,即可获得类似如下的结果:
mysql> SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE limit 30 ;
其中QUERY这一列会展示出执行的sql是什么,TRACE则是Object的结果,它是一段很长的JSON,我把结果拷贝出来:
{
"steps": [
{
"join_preparation": {
"select#": 1,
"steps": [
{
"expanded_query": "/* select#1 */ select `user_info`.`id` AS `id`,`user_info`.`username` AS `username`,`user_info`.`password` AS `password`,`user_info`.`real_name` AS `real_name`,`user_info`.`sex` AS `sex`,`user_info`.`birthday` AS `birthday`,`user_info`.`card_id` AS `card_id`,`user_info`.`mark` AS `mark`,`user_info`.`partner_id` AS `partner_id`,`user_info`.`group_id` AS `group_id`,`user_info`.`nickname` AS `nickname`,`user_info`.`avatar` AS `avatar`,`user_info`.`phone` AS `phone`,`user_info`.`add_ip` AS `add_ip`,`user_info`.`last_time` AS `last_time`,`user_info`.`last_ip` AS `last_ip`,`user_info`.`now_money` AS `now_money`,`user_info`.`brokerage_price` AS `brokerage_price`,`user_info`.`integral` AS `integral`,`user_info`.`sign_num` AS `sign_num`,`user_info`.`status` AS `status`,`user_info`.`level` AS `level`,`user_info`.`spread_uid` AS `spread_uid`,`user_info`.`spread_time` AS `spread_time`,`user_info`.`user_type` AS `user_type`,`user_info`.`is_promoter` AS `is_promoter`,`user_info`.`pay_count` AS `pay_count`,`user_info`.`spread_count` AS `spread_count`,`user_info`.`clean_time` AS `clean_time`,`user_info`.`addres` AS `addres`,`user_info`.`adminid` AS `adminid`,`user_info`.`login_type` AS `login_type`,`user_info`.`union_id` AS `union_id`,`user_info`.`open_id` AS `open_id`,`user_info`.`superior_user_id` AS `superior_user_id`,`user_info`.`is_indentor` AS `is_indentor`,`user_info`.`indentor_level_name` AS `indentor_level_name`,`user_info`.`direct_superior_user_id` AS `direct_superior_user_id`,`user_info`.`member_level_name` AS `member_level_name`,`user_info`.`upgrade_time` AS `upgrade_time`,`user_info`.`password_app` AS `password_app`,`user_info`.`store_name` AS `store_name`,`user_info`.`rank_indentor_id` AS `rank_indentor_id`,`user_info`.`rank_member_id` AS `rank_member_id`,`user_info`.`manage_pending` AS `manage_pending`,`user_info`.`manage_done` AS `manage_done`,`user_info`.`develop_pending` AS `develop_pending`,`user_info`.`develop_done` AS `develop_done`,`user_info`.`range_pending` AS `range_pending`,`user_info`.`range_done` AS `range_done`,`user_info`.`corpus_pending` AS `corpus_pending`,`user_info`.`corpus_done` AS `corpus_done`,`user_info`.`yeji_pending` AS `yeji_pending`,`user_info`.`yeji_done` AS `yeji_done`,`user_info`.`commission_pending` AS `commission_pending`,`user_info`.`commission_done` AS `commission_done`,`user_info`.`can_edit_material` AS `can_edit_material`,`user_info`.`poster_url` AS `poster_url`,`user_info`.`ctime` AS `ctime`,`user_info`.`mtime` AS `mtime`,`user_info`.`health_vip_ctime` AS `health_vip_ctime`,`user_info`.`makeup_vip_ctime` AS `makeup_vip_ctime`,`user_info`.`purchase_balance` AS `purchase_balance`,`user_info`.`ay_card_money` AS `ay_card_money`,`user_info`.`other_rank_id` AS `other_rank_id`,`user_info`.`superior_other_id` AS `superior_other_id`,`user_info`.`star_health_vip_ctime` AS `star_health_vip_ctime`,`user_info`.`star_makeup_vip_ctime` AS `star_makeup_vip_ctime`,`user_info`.`lock_money` AS `lock_money` from `user_info` where ((`user_info`.`nickname` = 'Ant') and (`user_info`.`ctime` > '2021-02-01'))"
}
] /* steps */
} /* join_preparation */
},
{
"join_optimization": {
"select#": 1,
"steps": [
{
"condition_processing": {
"condition": "WHERE",
"original_condition": "((`user_info`.`nickname` = 'Ant') and (`user_info`.`ctime` > '2021-02-01'))",
"steps": [
{
"transformation": "equality_propagation",
"resulting_condition": "((`user_info`.`nickname` = 'Ant') and (`user_info`.`ctime 以上是关于一文道尽数据库底层原理,探讨Mysql调优之道的主要内容,如果未能解决你的问题,请参考以下文章
Spark性能调优之道——解决Spark数据倾斜(Data Skew)的N种姿势