是否在使用Mysql时有以下疑问:
1、限制连接数时CPU占用量不大吞吐量也不高!
2、增大连接数后吞吐量提升不大却容易导致Mysql服务器卡死!
3、横向增加Mysql服务器时感觉并发能力提升也有限!
4、...
以下仅以mysql的innodb引擎说明,独享数据库服务器为例。
吞吐量瓶颈
mysql的吞吐量主要受:磁盘读写速度、索引高度、提取数据量、通信传输速度等影响。目前最突出的问题是各硬件的工作速度相差达大,CPU作为处理器,期处理速度最高,其次是内存,然后是磁盘和网络。处理速度的差异导致高速必需等待低速处理完才能继续,极大限制了处理速度。
数据落地必需存储在可永久保存数据的存储介质上,比如:SSD固态盘,HDD机械磁盘,U盘等。但这类存储介质普遍处理速度过低,特别是机械磁盘。早期多为机械磁盘存储数据,为了提升性能不仅使用了Btree结构存储、阵列存储、分布式存储等手段应对日益增加的流量。
不管使用了多少手段,要想提高整体性能就必需解决每个短板。通过分化、减少、整合、冗余、剥离等方式提升短板性能。mysql作为系统的短板,在处理前就必需谨慎对待每一个连接,最大化压缩处理时长。
单库并发量
单库并发极限主要受连接单次处理量与磁盘读写速度影响(假设CPU或内存充足),当连接单次处理量固定时其产生的读写量基本稳定,最终极限并发也将稳定。若一个连接单次读写10K,耗时9ms,磁盘平均读写速度在40M,则理论极限并发量=40M/10k=4000,需要处理进程数=4000/(1000/9)=36
SQL处理速度是毫秒级,一般独享服务器mysql处理速度在3ms左右(性能相对平稳),混合服务器处理速度可过0.3ms左右(性能波动大,主要还是硬件资源被瓜分)。若以一条SQL处理以3ms为准,则一个连接秒并发能力为:1000ms / 3 = 333条/秒(实际会偏少,主要还是索引树移动额外开销)。
显式事务是一个操作集合,多个事务之间如果有等待锁的情况会使用事务排队,造成处理性能急速下降,同时事务开启、提交、回滚均会占用处理时间(可以理解为一个正常SQL的时长)。
隐式事务是自动完成的(可以关闭),在mysql默认情况下每个SQL都会隐式执行事务开启和提交操作,其不会额外增加SQL执行时长,但有一个短暂的事务,并且会存在锁等待。
一个事务会占用一个连接,如果多个事务之间存在锁等待那还会出现排队,即事务处理时长越长性能越差(千万不要想着通过增加连接数来提升并发性能,连接数据越多进程之间的上下文切换越多性能反而越差)。
若一个显式事务内只有一个SQL处理,那这个事务的处理时长=事务开启SQL+处理SQL+事务(提交或回滚)SQL=3 * 3ms=9ms,其秒并发能力为:1000ms / 9 = 111条/秒。如果多个事务之间有锁等待,那涉及此事务的整体并发能力将无法超越111。
锁等待会极大限制事务并发能力,将锁进行分化或取消能显著提升事务并发能力。
提升单库并发
从使用角度来说,影响mysql性能发挥的主要因素有:SQL个数、SQL处理数据量、SQL锁等待。
减少SQL个数
- 避免循环SQL
- 避免重复SQL
- 精减合并处理量不大SQL
- 要求不高或变化不多的数据使用缓存
减少SQL处理数量
- 给查询条件最小范围的字段或多个组合字段创建索引
- 避免因纵向拆表带来连表查询(即一张表分割字段拆分成多张表)
- 通过冗余字段减少连表查询
- 避免limit中offset位置过大
- 大数据量查询时尽量以命中索引范围为限,进行union查询或循环处理
- 单库可使用存储过程或存储函数进行数据处理
减少SQL锁等待
- 减少共用记录集中修改
- 减少事务处理时长
- 尽可能使用行锁、间隙锁等,避免表锁
锁等待范围
锁等待对事务影响很隐秘,主要是因为加锁条件和范围很多时候不是很直观,一旦对加锁的情况理解不透彻导致锁等待过多或死锁。了解常用SQL加锁机制能很好的优化锁等待范围,直接提升事务性能。
加锁范围大小由索引命中范围确定,可以理解为索引范围就是锁,即使表中没有显式创建索引,数据库也会隐式创建一个聚集索引进行加锁。
除表锁外其它锁均是记录主键(聚集索引)锁定,即使命中的是其它索引最终锁还是记录锁定的主键,因此其它事务只要操作的数据未涉及锁定主键即不需要锁等待。
注意:mysql的锁必需加在记录上,欲加锁的记录全部不存在时加锁无效,最少有一条记录加锁有效!
select 可通过for update或for share加锁
独占锁(for update)加锁成功后其它事务不能读写锁定的记录
共享锁(for share)加锁成功后其它事务可读不可写锁定记录,并且其它事务也并行加共享锁,依次锁定生效。
update 自动选择加表锁、行锁、间隙锁等
通过索引命中范围进行加锁,所加的锁全部是独占锁,其它事务无法读写操作。
delete 自动选择加表锁、行锁、间隙锁等
通过索引命中范围进行加锁,所加的锁全部是独占锁,其它事务无法读写操作。
insert 自动加行锁等
所加的锁全部是独占锁,其它事务无法读写操作。
拆分锁
拆分锁能直接提升多进程并发能力,通过将事务加锁重叠高的记录进行拆分,能有效减少锁范围,提高多进程并行概率。如果出现表锁则必需优化掉。
首先锁定数据范围一定要足够小,然后将重叠很高的数据进行优化处理。一般有两种方案:缓存处理、分记录处理。
缓存处理
是将数据库里的数据写到缓存中,通过定时器更新到数据中,借助缓存的高速性能极速减少锁时长。缓存处理分单节点和多节点,缓存因不能参与数据库事务,比较适合数据实时严谨性要求不高的场景,比如:秒杀商品库存过滤处理,事务异常导致提前减完可以通过回填补上,再通过事务或多级库存进行校验完成严谨处理。
分记录处理
是将原本的记录按处理进程数或合适数量进行分开存储在数据库(分库、分表、分记录)中,使得处理进程数有多个独享处理记录错开锁重叠概率。分记录能充分利用数据库事务特性使得数据更严谨,同时比缓存处理更简单,但性能没有缓存处理快。比如:统计支付数据,将统计数据分成多条记录由各处理进程分开统计(可给每个处理进程分配一个独享记录处理),再通过查询进行汇总记录到总表中,最终统计出来的数据准确并实时性也能跟上。