一.前言
最近在运维系统,系统对客端突然报了403错误,从后台看发现了大量的慢SQL,导致查询超时,仔细分析我从来没见过那么厚颜无耻的SQL,一条SQL语句关联了一个大表(6000数据)查询了10次。我也很少见过一个SQL语句写了500多行。将一个很大的任务放在一个SQL里计算。以前能跑得起来是因为数据量少,现在表的数据量增加到6000万。性能急剧下降。
二、慢SQL分析(没见过如此厚颜无耻的SQL)
select
count(1) as cnt,
user_tag_user_life_period as label_value,
'user_tag_user_life_period' as label_english
from
data_tag.tag_user_attribute_all
where
one_id global in (
select
one_id
from
data_dwd.big_table final
where
(
(
case
when reg_time_taxfree is not null then 1
else 0
end
) + (
case
when reg_time_hotel is not null then 1
else 0
end
) + (
case
when reg_time_travel is not null then 1
else 0
end
) + (
case
when reg_time_invest is not null then 1
else 0
end
)
) >= 2
)
group by
user_tag_user_life_period
having
user_tag_user_life_period is not null
and user_tag_user_life_period <> ''
order by
cnt desc
limit
1
union all
select
count(1) as cnt,
user_tag_level_name_taxfree as label_value,
'user_tag_level_name_taxfree' as label_english
from
data_tag.tag_user_attribute_all
where
one_id global in (
select
one_id
from
data_dwd.big_table final
where
(
(
case
when reg_time_taxfree is not null then 1
else 0
end
) + (
case
when reg_time_hotel is not null then 1
else 0
end
) + (
case
when reg_time_travel is not null then 1
else 0
end
) + (
case
when reg_time_invest is not null then 1
else 0
end
)
) >= 2
)
group by
user_tag_level_name_taxfree
having
user_tag_level_name_taxfree is not null
and user_tag_level_name_taxfree <> ''
order by
cnt desc
limit
1
union all
select
count(1) as cnt,
user_tag_level_name_travel as label_value,
'user_tag_level_name_travel' as label_english
from
data_tag.tag_user_attribute_all
where
one_id global in (
select
one_id
from
data_dwd.big_table final
where
(
(
case
when reg_time_taxfree is not null then 1
else 0
end
) + (
case
when reg_time_hotel is not null then 1
else 0
end
) + (
case
when reg_time_travel is not null then 1
else 0
end
) + (
case
when reg_time_invest is not null then 1
else 0
end
)
) >= 2
)
group by
user_tag_level_name_travel
having
user_tag_level_name_travel is not null
and user_tag_level_name_travel <> ''
order by
cnt desc
limit
1
union all
select
count(1) as cnt,
user_tag_level_name_hotel as label_value,
'user_tag_level_name_hotel' as label_english
from
data_tag.tag_user_attribute_all
where
one_id global in (
select
one_id
from
data_dwd.big_table final
where
(
(
case
when reg_time_taxfree is not null then 1
else 0
end
) + (
case
when reg_time_hotel is not null then 1
else 0
end
) + (
case
when reg_time_travel is not null then 1
else 0
end
) + (
case
when reg_time_invest is not null then 1
else 0
end
)
) >= 2
)
group by
user_tag_level_name_hotel
having
user_tag_level_name_hotel is not null
and user_tag_level_name_hotel <> ''
order by
cnt desc
limit
1
union all
select
count(1) as cnt,
user_tag_taxfree_and_travel_sex as label_value,
'user_tag_taxfree_and_travel_sex' as label_english
from
data_tag.tag_user_attribute_all
where
one_id global in (
select
one_id
from
data_dwd.big_table final
where
(
(
case
when reg_time_taxfree is not null then 1
else 0
end
) + (
case
when reg_time_hotel is not null then 1
else 0
end
) + (
case
when reg_time_travel is not null then 1
else 0
end
) + (
case
when reg_time_invest is not null then 1
else 0
end
)
) >= 2
)
group by
user_tag_taxfree_and_travel_sex
having
user_tag_taxfree_and_travel_sex is not null
and user_tag_taxfree_and_travel_sex <> ''
order by
cnt desc
limit
1
union all
select
count(1) as cnt,
user_tag_taxfree_and_travel_user as label_value,
'user_tag_taxfree_and_travel_user' as label_english
from
data_tag.tag_user_attribute_all
where
one_id global in (
select
one_id
from
data_dwd.big_table final
where
(
(
case
when reg_time_taxfree is not null then 1
else 0
end
) + (
case
when reg_time_hotel is not null then 1
else 0
end
) + (
case
when reg_time_travel is not null then 1
else 0
end
) + (
case
when reg_time_invest is not null then 1
else 0
end
)
) >= 2
)
group by
user_tag_taxfree_and_travel_user
having
user_tag_taxfree_and_travel_user is not null
and user_tag_taxfree_and_travel_user <> ''
order by
cnt desc
limit
1
union all
select
count(1) as cnt,
user_tag_taxfree_and_hotel_sex as label_value,
'user_tag_taxfree_and_hotel_sex' as label_english
from
data_tag.tag_user_attribute_all
where
one_id global in (
select
one_id
from
data_dwd.big_table final
where
(
(
case
when reg_time_taxfree is not null then 1
else 0
end
) + (
case
when reg_time_hotel is not null then 1
else 0
end
) + (
case
when reg_time_travel is not null then 1
else 0
end
) + (
case
when reg_time_invest is not null then 1
else 0
end
)
) >= 2
)
group by
user_tag_taxfree_and_hotel_sex
having
user_tag_taxfree_and_hotel_sex is not null
and user_tag_taxfree_and_hotel_sex <> ''
order by
cnt desc
limit
1
union all
select
count(1) as cnt,
user_tag_taxfree_and_hotel_user as label_value,
'user_tag_taxfree_and_hotel_user' as label_english
from
data_tag.tag_user_attribute_all
where
one_id global in (
select
one_id
from
data_dwd.big_table final
where
(
(
case
when reg_time_taxfree is not null then 1
else 0
end
) + (
case
when reg_time_hotel is not null then 1
else 0
end
) + (
case
when reg_time_travel is not null then 1
else 0
end
) + (
case
when reg_time_invest is not null then 1
else 0
end
)
) >= 2
)
group by
user_tag_taxfree_and_hotel_user
having
user_tag_taxfree_and_hotel_user is not null
and user_tag_taxfree_and_hotel_user <> ''
order by
cnt desc
limit
1
union all
select
count(1) as cnt,
user_tag_last_year_amount as label_value,
'user_tag_last_year_amount' as label_english
from
data_tag.tag_user_attribute_all
where
one_id global in (
select
one_id
from
data_dwd.big_table final
where
(
(
case
when reg_time_taxfree is not null then 1
else 0
end
) + (
case
when reg_time_hotel is not null then 1
else 0
end
) + (
case
when reg_time_travel is not null then 1
else 0
end
) + (
case
when reg_time_invest is not null then 1
else 0
end
)
) >= 2
)
group by
user_tag_last_year_amount
having
user_tag_last_year_amount is not null
and user_tag_last_year_amount <> ''
order by
cnt desc
limit
1
union all
select
count(1) as cnt,
user_tag_last_year_frequency as label_value,
'user_tag_last_year_frequency' as label_english
from
data_tag.tag_user_attribute_all
where
one_id global in (
select
one_id
from
data_dwd.big_table final
where
(
(
case
when reg_time_taxfree is not null then 1
else 0
end
) + (
case
when reg_time_hotel is not null then 1
else 0
end
) + (
case
when reg_time_travel is not null then 1
else 0
end
) + (
case
when reg_time_invest is not null then 1
else 0
end
)
) >= 2
)
group by
user_tag_last_year_frequency
having
user_tag_last_year_frequency is not null
and user_tag_last_year_frequency <> ''
order by
cnt desc
limit 1;
这个SQL引发系统403,
一个重要设计缺陷是查询频繁。
第二个是这个查询放在对客端的微服务中开启了线程,这种设计严重影响对客端的性能和流畅度,这种业务应放在后台管理系统计算,而不是对客端。
第三、就是这个业务没有进行分解,是个大业务SQL 。
第四、这是一个无耻不考虑后果的SQL。没有考虑到单表数据量的暴增。
三、如何解决慢SQL和避免慢SQL
解决慢SQL(慢查询)和避免慢SQL是数据库优化中的关键任务。以下是一些建议和方法,可以帮助你解决和避免慢SQL:
1. 优化查询语句
- 使用索引:确保查询中使用的字段都已经建立了索引,这可以大大提高查询速度。
- **避免SELECT ***:只选择需要的字段,而不是选择所有字段。
- 使用连接(JOIN)代替子查询:当可能时,使用JOIN操作代替子查询。
- 优化WHERE子句:避免在WHERE子句中使用函数或计算,这会导致索引失效。
2. 优化数据库设计
- 正规化:通过正规化来减少数据冗余。
- 反正规化:在某些情况下,为了查询性能,可以故意引入一些冗余。
- 分区:将大表分区,可以提高查询性能。
3. 优化数据库配置
- 调整缓存大小:根据工作负载调整数据库的缓存大小。
- 调整I/O性能:确保数据库服务器有足够的I/O性能。
- 监控和调整并发连接数:根据实际需要调整最大并发连接数。
4. 使用分析工具
- 慢查询日志:启用数据库的慢查询日志功能,找出执行时间长的查询。
- EXPLAIN计划:使用EXPLAIN语句查看查询的执行计划,找出性能瓶颈。
5. 硬件和存储优化
- 使用更快的存储:例如,使用SSD替代HDD。
- 增加内存:为数据库服务器增加更多的内存。
- 优化I/O配置:确保I/O子系统(如RAID配置)是最优的。
6. 避免常见错误
- 避免在循环中执行查询:这会导致大量的数据库连接和查询。
- 避免使用LIKE操作符进行前缀模糊匹配:这会导致全表扫描。
7. 定期维护
- 更新统计信息:定期更新数据库的统计信息,以便优化器做出更好的决策。
- 重建索引:定期重建或优化索引,保持其性能。
8. 考虑使用缓存
- 查询缓存:某些数据库支持查询缓存,可以考虑启用。
- 外部缓存:如Redis或Memcached,用于缓存热点数据。
9. 考虑分布式解决方案
- 读写分离:将读操作和写操作分离到不同的服务器上。
- 分片:将数据分布到多个数据库服务器上。
10. 持续监控和学习
- 监控数据库性能:使用监控工具持续监控数据库性能。
- 持续学习:数据库技术和最佳实践在不断变化,保持学习是关键。
四、常见SQL优化方法
常见的SQL优化方法包括以下几个方面:
-
选择特定字段:尽量避免使用
SELECT *
,而是选择你真正需要的具体字段。这样可以减少不必要的数据传输和处理,从而提高查询效率。 -
使用索引:确保查询中使用的字段都已经建立了索引。索引可以大大提高查询速度,因为数据库可以快速定位到数据而不需要全表扫描。
-
优化WHERE子句:
- 避免在WHERE子句中使用函数或计算,因为这会导致索引失效。
- 尽量避免使用OR来连接条件,因为当OR两边的字段不是索引字段时,查询可能不走索引。
- 尽量避免在索引列上使用MySQL的内置函数。
-
优化JOIN操作:优先使用INNER JOIN,如果是LEFT JOIN,确保左边表的结果集尽量小。
-
使用LIMIT:当只需要一条或少数几条记录时,使用
LIMIT
来限制返回的结果集大小。 -
优化LIKE查询:尽量避免使用前缀模糊查询(如
LIKE '%li%'
),因为它会导致全表扫描。如果可能,尽量使用后缀模糊查询(如LIKE 'li%'
)。 -
避免使用子查询:当可能时,使用JOIN操作代替子查询。
-
优化排序操作:如果排序字段没有用到索引,尽量减少排序操作。
-
考虑表的设计:正规化和反正规化可以影响查询性能。确保你的表设计是合理的,并且考虑了查询性能。
-
使用分析工具:利用数据库的慢查询日志功能和EXPLAIN计划来找出性能瓶颈。
-
硬件和存储优化:确保数据库服务器有足够的硬件资源,如内存和I/O性能。使用更快的存储,如SSD,也可以提高性能。
-
避免常见错误:例如,避免在循环中执行查询,这会导致大量的数据库连接和查询。
-
定期维护:更新统计信息,重建或优化索引,以保持数据库性能。
-
考虑使用缓存:例如,使用查询缓存或外部缓存(如Redis或Memcached)来缓存热点数据。
-
持续监控和学习:使用监控工具持续监控数据库性能,并随着技术和最佳实践的发展保持学习。
结合这些策略和方法,你可以有效地优化SQL查询,提高数据库性能。