谈论集群之前,先说明一下项目发展的不同阶段对数据库性能的要求。
1.项目初始阶段
在这个阶段,主要是单节点数据库,注重的是可靠性。此时主要避免单点故障,做法是主从复制。
2.项目发展阶段
随着用户的增加,单个节点的读写性能逐渐成为瓶颈,此时主要是用到了读写分离和缓存。
数据库注重高可用性,即宕机后有段时间不能对外提供服务,高可用要求这个时间尽量短。总结一下,高可用就是合理的时间内给出合理的回复。主要从两个方面考虑,采用什么集群,多少时间内完成切换。
3.项目爆发期
这个时候主要注重高扩展性。主要做法是分库分表,使用多个数据库对外提供服务。
总结一下,当项目DBA介入之前,开发人员需要对数据库非常熟悉。
mysql具体操作:crud
索引:索引原理、索引优化、sql优化、字段优化
事务原理以及脏读、幻读等问题
主从复制原理及衍生技术:缓存、读写分离
集群方案:数据流、一致性可用性分析
mysql中插入数据,并不是直接向B+树中插入节点(innodb)。
对于聚集索引而言,插入的数据会先写入redolog和binlog,这样做磁盘IO的次数会少一些(顺序写比插入B+树的磁盘IO次数少),然后再通过其他线程,把redolog和binlog中的数据异步写入B+树中。
对于辅助索引而言,主要通过change buffer实现(提到change一般就是DML操作)。change buffer包含insert buffer,delete buffer,purge buffer。change buffer是一棵B+树,所有的非唯一辅助索引的 change buffer 存储在一棵B+树上,是在内存中,也会持久化到共享存储表空间(.ibd文件中)。对于非唯一辅助索引的DML操作不是每一次直接插入到索引页,而是先判断插入的非唯一辅助索引页是否在缓冲区中,如果在,直接插入;如果不在,则先放到一个change buffer 对象中,然后再以一定的频率将change buffer当中的数据合并到索引页中。
最后再讲一下,如果B+树写入失败,我们会认为是数据库宕机,可以利用redolog恢复之前的数据。如果是写了一部分数据后失败,mysql会使用double write策略,基本流程是在写入磁盘之前,先把相应的页在内存中复制一遍,如果写B+树中途失败,就使用复制的那一份再写。
mysql常规的查询流程,是将磁盘中的数据页读到内存中,通过二分查找找到相应的数据,整个过程较慢。自适应hash相当于mysql内部缓存,将常访问的数据存储在hash结构中,达到快速访问的效果。自适应hash索引通过缓冲池的B+树页来构建的,每张表对应一个hash。innodb存储引擎会自动根据访问的频率和模式来自动地为某些热点页建立hash索引。启用后,读取和写入速度提高2倍,辅助索引的操作性能提高5倍。
注意自适应hash索引只能用于等值查询,不能用于范围查询。
这里再提示一下,写入也有查询的操作。mysql会在写入前查询写入的是哪一个页,查询页的首指针、偏移量、写入大小等,所以写操作页同样可以使用自适应hash。
下面正式开始介绍集群的一些东西。
主从复制是集群中最重要的原理。主要是通过binlog实现。binlog文件的作用是恢复数据(增量数据)和用来主从复制(replication)。
这是较为安全的事务提交方式。主要步骤:
1.prepare,先将redolog持久化到磁盘。
2.prepare成功,再将事务日志持久化到binlog。
3.再在redolog上持久化commit操作。
开启的方法
innodb_support_xa=1
当然,还有一些其他的设置
# binlog文件前缀名
log-bin=mysql-bin
# 每提交n次事务,MySQL将进行一次fsync之类的磁盘同步指令来将binlog_cache中的数据强制写入磁盘。
sync_binlog=1
再介绍一下binlog文件的一些知识。
1.binlog文件会随服务的启动创建一个新文件。
2.通过flush logs
可以手动刷新日志,生成一个新的binlog文件。
3.通过show master status
可以查看binlog的状态。
4.通过reset master
可以清空binlog日志文件。
5.通过mysqlbinlog工具可以查看binlog日志的内容。
6.注意binlog文件是在事务提交之后才产生,因此回滚对binlog文件是没有影响的。
先查看binlog的状态,定位到具体要恢复的位置
show master status;
show binlog events in 'mysql57-bin.000001';
然后在命令行中进行恢复操作
# 查看binlog
mysqlbinlog mysql-bin.0000xx
# 恢复数据
mysqlbinlog mysql-bin.0000xx | mysql -u 用户名 -p 密码 数据库名
# 如mysqlbinlog mysql-bin.000002 --start-position 154 --stop-position 755 | mysql -uroot -p123456
# 不同选项
# --start-datetime:从二进制日志中读取指定等于时间戳或者晚于本地计算机的时间
# --stop-datetime:从二进制日志中读取指定小于时间戳或者等于本地计算机的时间
# --start-position:从二进制日志中读取指定 position 事件位置作为事件开始
# --stop-position:从二进制日志中读取指定 position 事件位置作为事件截止
项目发展是有序的,从单节点到多个节点,怎么做到无痛升级?应用程序尽量不改动,使用proxy层来实现功能。这样就可以不用在应用层写具体的策略(读写分离等),方便日后的扩展。
代理层的思想可以用到很多地方。计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。
对于mysql代理层而言,代理层与服务器之间的通信依然采用mysql协议,所以服务器并不需要改变协议,原来用的什么组件还是可以用什么,屏蔽掉了后面的实现细节和具体策略。
mysql-proxy是mysql官方提供的mysql中间件服务,上游可接入若干个mysql-client,后端可连接若干个mysql-server。它使用mysql协议,任何使用mysql-proxy的下游无需修改任何代码,即可迁移至mysql-proxy上。
工作原理是,mysql-proxy向用户提供了6个 hook 点,让用户通过Lua脚本来完成功能,这些 hook 点是以函数的形式提供的,用户可以实现这些函数,在不同事件、不同操作发生时,来定义我们需要的行为。
这里不建议使用mysql-proxy,因为该组件已经不再维护,如果组件出了问题,需要我们自己去修改代码。这也是一个思想,选用工具时尽量选用社区活跃的工具,如mycat。
分库分表主要用在数据库数据量猛增,单个节点无法满足需求的时候,主要是为了解决由于数据量过大而导致数据库性能降低的问题,采取分库分表从而达到提升数据库性能的目的。
主要有两种场景:一是磁盘不够,即数据较多时;二是数据库查询效率急剧降低时。
具体来说,什么时候分表分库?当内存中的查询缓存经常淘汰厂访问的数据时,就要考虑分库分表了。这里有一个经验值,当一个表的数据量达到500w条或者.ibd表文件大小大于2G时,就要分表了;分库的依据是,数据库中的数据要支撑3年且保持稳定。
拆分方法主要是水平拆分和垂直拆分。这里以分表为例,分库也是类似的原理。
从图中可以看出,每个库(表)的结构都一样,但是每个库(表)的数据内容都不一样,并且并集是全量数据。
水平拆分的优点是库(表)的数据量维持在合理范围,一定程度提升性能(减小了B+树的层高等值查询时性能会提升),提供了均衡的负载能力。缺点是扩容难度大,因为扩容前的数据已经按照原来的规则写入,扩容会变更规则,使得查询变得困难;另外,拆分规则选取较难,关联查询也较为困难。水平拆分虽然在使用层没有影响,包括多表联合查询,mysql会自己处理。只是性能有所降低。此外,对于范围查询,性能也有所降低。
垂直拆分需要自己根据业务场景、不同功能进行拆分,需要有一个统一的字段去建立联系。
最好在建表初期就按照这样来设计,这也是一个范式。
垂直拆分和水平拆分一样,并集是全量数据。不一样的是,每个库(表)的结构都不一样,每个库(表)的至少有一列数据一样。
垂直拆分优点是业务清晰,维护简单。缺点是在单表数据量大的情况下,该表依然查询效率低。另外,和水平拆分类似,关联查询困难,数据分布在不同的文件,mysql需要一些join的操作。