为什么不要问我DB极限QPS/TPS
背景 相信很多开发都会有这个疑问,DB到底可以支撑多大的业务量,如何去评估?对于这个很专业的问题,DBA也没有办法直接告诉你,更多的都是靠经验提供一个看似靠谱的结果,这里主要说明数据库容量评估的难点。
定性分析
借用学校时候做物理题的一个思考方法 -- 极限法 ;我们假设两种极限场景: 极限场景一,所有SQL 都是主键等值查询。极限场景二,所有SQL 都是走不上索引的全表扫描。 这两种场景下大家都能够一眼看出数据库的支撑能力,在场景一和场景二下会有很大的差别。当然,我们现实的业务场景,位于两种极限场景之间,这个时候很难简单粗暴的说当前实例可以支撑多少业务量,因为缺少信息输入。DBA 同学一定会和研发同学进行详细的沟通,确认数据库运行SQL 的类型,以及不同类型SQL 的执行频率,所涉及表的数据量情况,综合评估一个可以支撑的性能区间,作为上线前的基本容量建设模型。
随着系统上线后,数据库系统就会一直处于一种变化的状态。变化一,SQL 类型,随着业务逻辑不断丰富,运行SQL 开始逐渐变得更复杂,从最开始设计的几类SQL 到 十几类SQL 甚至 几十类SQL,这就要求我们对新上线的SQL 书写质量有一个保障机制,就是大家熟悉的SQL Review,当前有人工Review 和>
常见影响性能案例
大规模数据导出功能
相信很多业务都遇到过数据导出,明细展示这方面的需求,sql基本上都是先求一个数据的总和然后,limit n,m分页查询,这样的问题就在于,在扫描前面的数据时是不会有性能问题的,当n值越大,偏移量越多,扫描的数据就越多,这个时候就会产生问题,一个本来不的sql就会变成慢sql,导致DB性能下降。针对这种问题DBA都会建议开发将limit n,m改为id范围的查询,或者进行业务改造对于一些不必要的场景只展示前几百条,只需要进行一次分页即可。
类似sql模式:
select count(*) from table_name_1;select * from table_name_1 limit n,m;(n值越大性能越差) 建议改造成:
select * from table_name_1 where>
ERP类系统使用聚合函数或者分组排序
类似仓库内管理系统会需要展示很多统计信息,很多开发会选择在DB端计算出结果直接展示,问题在于sum,max,min类的聚合函数在DB端执行会消耗到CPU资源,如果这个时候还遇到索引不合理的情况,往往会带来灾难性的后果。这种情况DB端除了增加索引,对CPU的消耗是无法优化的,所以DB性能必然下降。一般这种情况DBA会建议能在程序端计算的就不要放在DB端,或者直接接搜索引擎。
类似sql模式:
select sum(column_name) as column_1 from table_name_1;or select distinct cloumn_name from table_name_1 group by column_name_1 order by column_name_1;
错误使用子查询
在DB端执行去重,join以及子查询等操作的时候,mysql会自动创建临时表 。
DB自动创建临时表的情况有如下几种
1. Evaluation of UNION statements.
2. Evaluation of some views, such those that use the TEMPTABLE algorithm, UNION, or aggregation.
3. Evaluation of derived tables (subqueries in the FROM clause).(这个是本节关注的重点)
4. Tables created for subquery or semi-join materialization (see Section 8.2.1.18, “Subquery Optimization”).
5. Evaluation of statements that contain an ORDER BY clause and a different GROUP BY clause, or for which the ORDER BY or GROUP BY contains columns from tables other than the first table in the join queue.
6. Evaluation of DISTINCT combined with ORDER BY may require a temporary table.
7. For queries that use the SQL_SMALL_RESULT option, MySQL uses an in-memory temporary table, unless the query also contains elements (described later) that require on-disk storage.
8. Evaluation of multiple-table UPDATE statements.
9. Evaluation of GROUP_CONCAT() or COUNT(DISTINCT) expressions.
在mysql中,对于子查询,外层每执行一次,内层子查询要重复执行一次,所以一般建议用join代替子查询。
下面举一个子查询引起DB性能问题的例子
Query1:select count(*) from wd_order_late_reason_send wrs left join wd_order_detail_late_send wds on wrs.store_code = wds.store_code;
下面是执行计划:
*************************<strong> 1. row </strong>***********************<strong> id:
1 select_type: SIMPLE
table: wrs type:
ALL possible_keys:
NULL key: NULL
key_len: NULL
ref: NULL
rows: 836846
Extra: NULL
</strong>***********************<strong> 2. row </strong>*************************
id: 1
select_type: SIMPLE
table: wds
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 670612
Extra: Using where; Using join buffer (Block Nested Loop)
Query2:select count(*) from (select wrs.store_code from wd_order_late_reason_send wrs left join wd_order_detail_late_send wds on wrs.store_code = wds.store_code) tb;
执行计划如下
*************************<strong> 1. row </strong>***********************<strong> id:
1 select_type:
PRIMARY table: <derived2>
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 561198969752
Extra: NULL
</strong>***********************<strong> 2. row </strong>***********************<strong>
id: 2
select_type: DERIVED
table: wrs
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 836846
Extra: NULL
</strong>***********************<strong> 3. row </strong>*************************
id: 2
select_type: DERIVED
table: wds
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 670612
Extra: Using where; Using join buffer (Block Nested Loop)
这两个sql结果相同,唯一不同的是第二条sql使用了子查询。通过执行计划可以看出(排除没有索引部分)两个sql最大的差别就是第二个sql有derived table并且rows是561198969752,出现这个数值是因为在select count(*) 每次计数的时候子查询的sql都会执行一遍,所以最后是子查询join的笛卡尔积 。因为内存中用于进行join操作的空间有限,这个时候就会使用磁盘空间来创建临时表,所以当第二种sql频繁执行的时候会有磁盘被撑爆的风险。 想要了解更多关于子查询的优化可以参考下面这个链接link
慢sql
这里我们所说的慢sql主要指那些由于索引使用不正确或没有使用索引产生的,一般可以通过增加索引。一个合理的索引对一条sql性能的影响是非常巨大的。索引的主要目的是为了减少读取的数据块,也就是我们常说的逻辑读,读取的数据块越少,sql效率越高。另外索引在一定程度上也可以减少CPU的消耗,例如排序,分组,因为索引本来就是有序的。
说到逻辑读,对应的就会有物理读,在mysql服务端是有buffer pool来缓存硬盘中的数据,但是这个buffer pool的大小跟磁盘中数据文件的大小是不等的,往往buffer pool会远远小于磁盘中数据的大小。buffer pool会有一个LRU链表,当从磁盘中加载数据块到内存中(这个就是物理读)发现没有空间的时候会优先覆盖LRU链表中的数据块。当一条sql没有合理的索引需要扫描大量的数据的时候,不光要扫描内存中的许多数据块,还可能需要从磁盘中加载不同不存在的数据块到内存中进行判断,当这种情况频繁发生的时候,sql性能就会急剧下降,因而也影响了DB实例的性能。
以下表格是访问不同存储设备的rt,由此可见一个合理的索引的重要性。
类别吞吐量响应时间访问L1
Cache
0.5ns
访问L2
Cache
7ns
内存访问
800M/s
100ns
机械盘
300M/s
10ms
SSD
300M/s
0.1~0.2ms
日志刷盘策略不合理
目前集团mysql大部分使用的都是innodb存储引擎,因此在每条DML语句执行时不光会记如binlog还有记录innodb特有的redo log和undo log。这些日志文件都是先写入内存中然后在刷新到磁盘中。在server端有两个参数分别控制他们的写入速度。innodb_flush_log_at_trx_commit控制redo log写入模式,sync_binlog控制binlog写入模式。
通过以上表格可以了解到,在使用线上默认配置的情况下每次commit都会刷redo log到磁盘,也就是说每次写入都会伴随着日志刷盘的操作,需要消耗磁盘IO,所以在高TPS或者类似业务大促情况下,DBA可以调整这个参数,来提升DB支撑TPS的能力。
BP设置过小
前面已经提到sql在读写数据的时候不会直接跟磁盘交互,而是先读写内存数据,因为这样最快。但是考虑到成本问题BP(buffer pool)大小是有限的,不可能跟数据文件同等大小,所以如果BP设置不合理就会导致DB的QPS TPS始终上不去。下面我们具体分析一下。
mysql buffer pool中包含undo page,insert buffer page,adaptive hash index,index page,lock info,data dictionary等等DB相关信息,但是这些page都可以归为三类free page,clean page,dirty page.buffer pool中维护了三个链表:free list,dirty list,lru list
free page:此page未被使用,此种类型page位于free链表中
clean page:此page被使用,对应数据文件中的一个页面,但是页面没有被修改,此种类型page位于lru链表中
dirty page:此page被使用,对应数据文件中的一个页面,但是页面被修改过,此种类型page位于lru链表和flush链表中
当BP设置过小的时候,比如BP 10g 数据文件有200g 这个时候有大量的select或者dml语句,mysql就会频繁的刷新lru list或者dirty list 到磁盘,大部分时间消耗在刷磁盘上,而不是业务sql处理上,这个时候就会导致业务TPS QPS始终上不去,伴随着DB内存命中率降低 。通常这个时候的解决办法是需要DBA调整一下实例BP的大小。
硬件问题
就像生活中会有意外一样,在排除了之前那些因素之后,还会存在因为硬件故障或者参数设置不合理导致DB性能抖动的情况,如果不能立即修复,DBA一般只能通过迁移实例的方式来消除影响。
写在后面
经过上面几个情景的描述,我们可以把影响线上DB性能的因素归为三类:1、业务逻辑问题 2、DB端设置问题 3、硬件问题。因为硬件问题属于小概率事件,所以影响线上DB性能的主要是前面两类因素,也因此不同的业务场景下,DB的表现是天差地别的。
运维网声明
1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网 享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com