Mysql InnoDB 如何做数据 非停机迁移?
如何确保备份的库、表是同一逻辑时间点?
比如,转账操作,同时更新两条数据,没有使用事务,一条成功后,另一条成功前做备份。
比如,余额表,订单表两个表的操作。
- mysql 全局锁。
- 全局视图。(MVCC Read View)
Mysql 线程问题
Mysql 的连接池是否等价于线程池?
Mysql建立连接后,同时提交多个sql,是并行还是串行?是否有数量限制?并发场景是否会导致等待超时?
Mysql 锁问题
表锁 MetaData Lock
除了对数据的表锁外。还有对表结构的表锁。
在需要对线上环境表结构进行调整时,如何安全的执行?
在一个正在执行的事务中间(未提交前),执行表结构调整(加MDL写锁)。此时,是否能够加成功?如果成功,事务中后续对表的操作结果会如何?如果不能说明原因。
答:此时能够加锁成功(加锁MDL写锁),但是会阻塞,导致后续其他客户端的读操作也同步进入阻塞(因为存在MDL写锁)。这种情况与ReentrantReadWriteLock情况一致。
那么如何避免这个问题呢?
- 避免使用长事务。如果正在执行的事务一直没有提交,后续的MDL读、写操作一直阻塞,导致资源占用过多。
- 为MDL写锁增加时间限制,类似与tryLock(overTime),超时自动释放。
行锁&事务的顺序
假如现在有这么一个场景:用户用账户余额买了一个商家的物品。
在这个场景中,我们要做三个事情,1. 扣减用户账户余额。2. 增加商家账户余额 3. 生成一条订单记录。
通常我们会使用事务来保证三个操作的原子性。那么,是否有思考过到底如何才能达到性能最优呢?
试想,我们三个操作中,容易产生冲突(锁等待)的地方可能只有操作2,增加商家账户余额。比如,商家在大促时,可能有N多的用户同时购买,对用户扣减,新增订单这两个操作来说,其实就不存在锁等待问题。因此,我们可以使用,1、3、2或者3、1、2的顺序,先执行两个不存在等待的操作,哪怕在最后异步出现互斥,导致等待,也能确保对事务执行时间影响最小。
死锁&死锁检测
还是以上边 用户购买商家物品为例,在大促时,有可能出现数据库服务cpu使用率100%,每秒执行事务数量还不到100的情况。这种情况其实就有可能是死锁检测导致的。
死锁的概念就不多描述了。
那么什么是死锁检测呢?
我们知道解决死锁的方式之一就是确保加锁的顺序一致,避免互相等待。但是对于数据库来说,很难去保证。所以Innodb 提供了死锁检测功能,在对数据加锁时,会发起死锁检测,如果存在冲突,则回滚一个事务确保其他事务执行,这种机制能有效避免死锁(Mysql死锁等待超时时间为50s)所以默认也是开启的,但同时,这个检测也会带来额外的性能消耗。
如果,同一时间有1000个事务加锁,那么检测就需要判断100W次,性能可想而知,并发带来的成本急剧增加。因此可能出现挂掉的情况。
那么应该如何处理呢? 高级的直接改数据库源码,控制存储引擎并发数量。
InnoDB结构
Buffer Pool 大小调到最大 是否等同于Redis?
不同维度的东西不好拿来做比对。
首先,Buffer Pool 作为缓存,首要解决的问题是减少磁盘IO,减少随机写磁盘的性能消耗。读只是其附加价值。
相对于Redis本身就是为了高性能的读取来说,本质就不一样。而且Redis还对存储结构做了较多的设计,比如动态字符串,Hash表,压缩列表,跳表。
不过总体而言,Buffer Pool设置的足够大,确实对于查询性能来说会提升不少。
Buffer Pool & Change Buffer
Change Buffer 作为 Buffer Pool的一部分,他的空间来自于Buffer Pool。可以通过 参数 innodb_change_buffer_max_size 来设置占比。
Change Buffer 的作用主要体现在,如果我们要对一个数据进行更新操作,假如这个数据不在Buffer Pool中那么可能会有两种情况。一种是加载到Buffer Pool再执行更新,另一种就是直接使用Change Buffer 将更新操作记录在Change Buffer 中,后续由线程刷到磁盘中(过程怎么保证下一步再讨论)。
什么时候可以使用Change Buffer
使用Change Buffer 的条件时非唯一索引,表中有唯一索引字段,在新增数据时,需要校验唯一索引字段是否重复,此时就不能使用Change Buffer,需要去磁盘读取并校验。如果非唯一索引此时可以直接使用Change Buffer 提升性能(唯一索引还有必要用么??)。
唯一索引的使用还是有必要的,在一些场景中,新增完数据可能会立刻需要查询(更新当前页面),这种时候,如果能够使用唯一索引还是使用唯一索引比较好。非唯一索引虽然会将更新动作直接记录到Change Buffer 中,但是 在执行查询该数据时,会触发Merge操作,等同于Buffer Pool 的刷脏。因此,这种情况,反而多了维护Change Buffer的资源消耗(有点想干掉Change Buffer 了!!!)。当然对于,日志、操作记录这种不需要立刻查询的场景还是非常友好的。
Change Buffer & Redo Log
一个更新操作在写入Change Buffer 后仍然需要在事务提交时,写Redo Log。因此持久化操作还是靠Redo Log保证。我们知道,Redo Log虽然保证了持久化,但是一般用于故障恢复。对于写磁盘数据页还是由后台线程对Buffer Pool执行刷脏来完成。那么,Change Buffer 中的数据 何时才会"刷脏"写入Buffer Pool呢?有以下几个条件:
- 内存空间不足时
- 后台线程定期执行
- Redo Log写满时
- 数据库正常关闭时
- 访问这个数据页时
Buffer Poll 持久化
Buffer Pool 数据需求持久化到磁盘。时机有四种:
- 内存空间不足时
- Redo Log写满时
- 后台线程定期执行
- 数据库正常关闭时
这四种,前两种,会对mysql性能产生影响。
内存不足时(Buffer Pool没有额外的内存加载要操作的数据),需要淘汰一些数据(内存淘汰),再去加载要操作的数据,此时会抖一下,拖慢一些速度。
Redo Log写满时,影响就比较大,因为无法进行写操作了,必须要执行刷脏了(磁盘IO),因此我们要避免Redo Log写满这种操作。那如果要避免,就需要提升 3、4情况的刷脏效率。而刷脏效率设置 通过参数 innodb_io_capacity 可以灵活的配置,这个参数其实就是告诉mysql我这台服务器性能怎么样,你按照这个设置来控制刷脏速度。如果一台高性能机器,innodb_io_capacity 设置了一个很低的值,那么Mysql刷脏效率也会很低。
另外 还需要再提一个参数,innodb_flush_neighbors,这个参数控制刷脏时是否连带着旁边的脏页也给刷新掉(连坐), 8.0 以后这个配置默认是关闭的。因此8.0以前,一次刷脏有可能引发一场血案。
Mysql 索引问题
为什么有时候会选错索引
比如 表 t 有 id、a、b两个字段,a、b字段均有索引。 插入10W条数据 (1,1,1)(2,2,2)...
查询语句: select * from t where a > 0 and a < 1000 and b > 1000 and b < 5000 order by b;
此时 明显走 a 索引会 性能最佳,但是 explain 时发现,选择了 b 索引 原因就是 order by 字段导致 优化器认为,排序的性能消耗 要比 扫描更多行数据 代价更高。
为什么正确使用了索引还是会存在慢查
表被锁住,导致查询等待锁释放出现慢查。
RR隔离级别下,MVCC版本链太长。开启事务后,对数据查询之前存在大量的修改变更。