是否在使用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个数
  1. 避免循环SQL
  2. 避免重复SQL
  3. 精减合并处理量不大SQL
  4. 要求不高或变化不多的数据使用缓存


减少SQL处理数量
  1. 给查询条件最小范围的字段或多个组合字段创建索引
  2. 避免因纵向拆表带来连表查询(即一张表分割字段拆分成多张表)
  3. 通过冗余字段减少连表查询
  4. 避免limit中offset位置过大
  5. 大数据量查询时尽量以命中索引范围为限,进行union查询或循环处理
  6. 单库可使用存储过程或存储函数进行数据处理


减少SQL锁等待
  1. 减少共用记录集中修改
  2. 减少事务处理时长
  3. 尽可能使用行锁、间隙锁等,避免表锁


锁等待范围

锁等待对事务影响很隐秘,主要是因为加锁条件和范围很多时候不是很直观,一旦对加锁的情况理解不透彻导致锁等待过多或死锁。了解常用SQL加锁机制能很好的优化锁等待范围,直接提升事务性能。

加锁范围大小由索引命中范围确定,可以理解为索引范围就是锁,即使表中没有显式创建索引,数据库也会隐式创建一个聚集索引进行加锁。

除表锁外其它锁均是记录主键(聚集索引)锁定,即使命中的是其它索引最终锁还是记录锁定的主键,因此其它事务只要操作的数据未涉及锁定主键即不需要锁等待。

注意:mysql的锁必需加在记录上,欲加锁的记录全部不存在时加锁无效,最少有一条记录加锁有效!


select  可通过for update或for share加锁

独占锁(for update)加锁成功后其它事务不能读写锁定的记录

共享锁(for share)加锁成功后其它事务可读不可写锁定记录,并且其它事务也并行加共享锁,依次锁定生效。


update 自动选择加表锁、行锁、间隙锁等

通过索引命中范围进行加锁,所加的锁全部是独占锁,其它事务无法读写操作。


delete 自动选择加表锁、行锁、间隙锁等

通过索引命中范围进行加锁,所加的锁全部是独占锁,其它事务无法读写操作。


insert 自动加行锁等

所加的锁全部是独占锁,其它事务无法读写操作。


拆分锁

拆分锁能直接提升多进程并发能力,通过将事务加锁重叠高的记录进行拆分,能有效减少锁范围,提高多进程并行概率。如果出现表锁则必需优化掉。

首先锁定数据范围一定要足够小,然后将重叠很高的数据进行优化处理。一般有两种方案:缓存处理、分记录处理。


缓存处理

是将数据库里的数据写到缓存中,通过定时器更新到数据中,借助缓存的高速性能极速减少锁时长。缓存处理分单节点和多节点,缓存因不能参与数据库事务,比较适合数据实时严谨性要求不高的场景,比如:秒杀商品库存过滤处理,事务异常导致提前减完可以通过回填补上,再通过事务或多级库存进行校验完成严谨处理。


分记录处理

是将原本的记录按处理进程数或合适数量进行分开存储在数据库(分库、分表、分记录)中,使得处理进程数有多个独享处理记录错开锁重叠概率。分记录能充分利用数据库事务特性使得数据更严谨,同时比缓存处理更简单,但性能没有缓存处理快。比如:统计支付数据,将统计数据分成多条记录由各处理进程分开统计(可给每个处理进程分配一个独享记录处理),再通过查询进行汇总记录到总表中,最终统计出来的数据准确并实时性也能跟上。