MySQL数据库面试知识点第四期

115 阅读7分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

数据库是软件开发中非常重要的一环,数据是这个时代真正值钱的东西。本期就来讨论关于数据库在面试中会遇到的一些问题,其中包含个人一些理解,由于本人学识有限,难免会有纰漏之处,还望读者能多多包涵。

上一期(传送门)讲了主要的MYSQL数据类型、约束、存储过程等内容,本期讲解关于生产环境下的一些问题。


一、主从复制

主从复制,就是建立一个和主数据库完全一样的数据库环境,称为从数据库。在赋值过程中,一个服务器充当主服器,而另外一台服务器充当从服务器。主服务器会将更新信息写入到一个特定的二进制文件中,并会维护文件的一个索引用来跟踪日志循环,这个日志可以记录并发送到从服务器的更新中去。

之所以这么做,主要是希望主数据库服务器出故障后,可切换到从数据库继续工作,避免数据丢失和服务中断。除此之外,也可也负载均衡,当I/O访问频率过高,单机无法满足,此时从数据库可以分担一部分,降低磁盘I/O访问的评率,提高单个机器的I/O性能。

最后就是读写分离。使数据库能支持更大的并发。在报表中尤其重要。由于部分报表sql语句非常的慢,导致锁表,影响前台服务。如果前台使用master,报表使用slave,那么报表sql将不会造成前台锁,保证了前台速度。也就是在从服务器执行查询工作(读),降低主服务器压力。


二、MySQL的GTID

GTID其全称是Global Transaction Identifier,也称之为全局事务ID。从MySQL 5.6.2开始支持,MySQL 5.6.10后完善,可简化MySQL的主从切换以及Failover。GTID是由UUID+TID组成的,server_uuid和transaction_id两个共同组成一个GTID。即:GTID = server_uuid:transaction_id。

GTID有如下几点作用:

① 根据GTID可以知道事务最初是在哪个实例上提交的。

② GTID的存在方便了Replication的Failover。因为不用像传统模式复制那样去找master_log_file和master_log_pos。

③ 基于GTID搭建主从复制更加简单,一个事务对应一个唯一GTID,一个GTID在一个服务器上只会执行一次。

其主从复制过程大致如下:

1、master更新数据时,会在事务前产生GTID,一同记录到binlog日志中。
2、slave端的i/o 线程将变更的binlog,写入到本地的relay log中。
3、sql线程从relay log中获取GTID,然后对比slave端的binlog是否有记录。
4、如果有记录,说明该GTID的事务已经执行,slave会忽略。
5、如果没有记录,slave就会从relay log中执行该GTID的事务,并记录到binlog。
6、在解析过程中会判断是否有主键,如果没有就用二级索引,如果没有就用全部扫描。


三、分库分表和表分区

技术很多时候都是相通的。重要的是编程思想,思想是最重要的。当数据量大的时候,需要具有分的思想去细化粒度。当数据量太碎片的时候,需要具有合的思想来粗化粒度。

很多技术都运用了分的编程思想,如下例子:

  • 集中式服务发展到分布式服务
  • 从Collections.synchronizedMap(x)到1.7ConcurrentHashMap再到1.8ConcurrentHashMap,细化锁的粒度的同时依旧保证线程安全
  • 从AtomicInteger到LongAdder,ConcurrentHashMap的size()方法。用分散思想,减少cas次数,增强多线程对一个数的累加
  • JVM的G1 GC算法,将堆分成很多Region来进行内存管理
  • Hbase的RegionServer中,将数据分成多个Region进行管理
  • 平时开发是不是线程池都资源隔离

很多技术也运用到了合的编程思想,举例如下:

  • TLAB(Thread Local Allocation Buffers),线程本地分配缓存。避免多线程冲突,提高对象分配效率
  • 逃逸分析,将变量的实例化内存直接在栈里分配,无需进入堆,线程结束栈空间被回收。减少临时对象在堆内分配数量
  • CMS GC算法下,虽然使用标记清除,但是也有配置支持整理内存碎片。如:-XX:UseCMS-CompactAtFullCollection(FullGC后是否整理,Stop The World会变长)和-XX:CMSFullGCs-BeforeCompaction(几次FullGC之后进行压缩整理)
  • 锁粗化,当JIT发现一系列连续的操作都是对同一对象反复加锁和释放锁,会加大锁同步的范围
  • kafka的网络数据传输有一些数据配置,减少网络开销。如:batch.size和linger.ms等等
  • 平时开发是不是都个叫批量获取接口

分库分表:当一张表随着时间和业务的发展,库里表的数据量会越来越大。数据操作也随之会越来越大。一台物理机的资源有限,最终能承载的数据量、数据的处理能力都会受到限制。这时候就会使用分库分表来承接超大规模的表,单机放不下的那种。区别于分区的是,分区一般都是放在单机里的,用的比较多的是时间范围分区,方便归档。只不过分库分表需要代码实现,分区则是mysql内部实现。分库分表和分区并不冲突,可以结合使用。

image.png

分库分表标准:存储占用100G+;数据增量每天200w+; 单表条数1亿条+。


四、自增ID与UUID

在InnoDB存储引擎中,主键索引作为聚簇索引存在。主键索引的B+树叶子节点上存储了主键索引以及全部的数据(按照顺序),如果主键索引是自增ID,那么只需要不断向后排列即可。MySQL官方不建议使用UUID与不连续不重复的雪花ID。理由有以下3点:

  • 使用自增id的索引内部结构 自增的主键是顺序的,Innodb将每一条记录都存储在一条记录后面,当达到页面的最大填充因子(15/16预留1/16做修改),下一条记录就会写入新的页中,按照这种方式加载,主键页就会近乎于顺序的记录填满,提升了页面的最大填充率,新插入的数据一定会有在原有的最大数据的下一行,MySQL定位和寻址很快,减少页分裂与碎片产生
  • 使用uuid索引的内部结构 innodb无法将新行插入到索引的后面,写入是乱序的,innodb不得不频繁做页分裂操作,为新行分配空间,页分裂导致大量数据移动,页会变得稀疏并被不规则填充,会导致页有碎片,将随机值载入聚族索引以后,会需要做一次optimeize table来重建表并优化页的填充,需要一定的时间消耗
  • 可在逻辑上使用uuid,物理上使用自增id

从生产角度来讲,UUID的性能与自增ID相比差多少,这取决于UUID的生成算法。举个例子MongoDB所采用的ObjectId就是一个比较优秀的UUID策略,其组成是时间戳+机器码+进程码+自增数,其中机器码和进程码都可以一次性生成,这样得到一个ObjectId仅仅之比自增ID多了一个时间戳的获取。另外考虑到自增ID都要做主键唯一索引,而UUID可以只做索引,不做唯一索引(利用其特性,可以不考虑唯一性过滤),其性能可以说并不比自增ID差。至于使用UUID还是自增ID主要还是看项目是否足够庞大,数据量是否足够多。增ID使用简单,方便。UUID相对麻烦一些,涉及到UUID算法的选取、程序的嵌入等等。具体选择要看实际情况。

至此关于数据库方面的更新告一段落,后续将更新算法类文章,敬请关注。