从放弃到入门-MySQL

264 阅读34分钟

InnoDB有多少种日志

错误日志:记录出错信息,也记录一些警告信息或者正确的信息
查询日志:记录所有对数据库请求的信息,不论这些请求是否得到了正确的执行
慢查询日志:设置一个阈值,将运行时间超过该值的所有SQL语句都记录到慢查询的日志文件中
二进制日志:记录对数据库执行更改的所有操作
中继日志
事务日志

事务的4种隔离级别

读未提交
读已提交
可重复读
串行化

B+树索引和哈希索引的区别

B+树是一个平衡的多叉树,从根节点到每个叶子节点的高度差值不超过1,而且叶子节点的指针相互链接,是有序的;
哈希索引就是采用一定的哈希算法,把键值换算成新的哈希值,检索时不需要类似B+树那样从跟节点到叶子节点逐级查找,只需要一次哈希算法即可,是无序的。 哈希索引的优势:
等值查询。哈希索引具有绝对优势(前提是:没有大量重复键值,如果大量重复键值时,哈希索引的效率很低,因为存在所谓的哈希碰撞问题。)
哈希索引不适用的场景:
不支持范围查询
不支持索引完成排序
不支持联合索引的最左前缀匹配规则

存储引擎有哪些

MyISAM、InnoDB、BDB(BerkeleyDB)、Merge、Memory(Heap)、Example、Federated、Archive、CSV、Blackhole、MaxDB 等等十几个引擎
MyISAM:成熟、稳定、易于管理,快速读取。一些功能不支持(事务等),表级锁。
InnoDB:支持事务、外键等特性、数据行锁定。空间占用大,不支持全文索引等。

聚簇和非聚簇索引

B+树是左小右大的顺序存储结构,节点只包含id索引列,而叶子节点包含索引列和数据,这种数据和索引在一起存储的索引方式叫做聚簇索引,一张表只能有一个聚簇索引。假设没有定义主键,InnoDB会选择一个唯一的非空索引代替,如果没有的话则会隐式定义一个主键作为聚簇索引。
非聚簇索引(二级索引)保存的是主键id值,这一点和myisam保存的是数据地址是不同的。

索引失效的场景

  • 以“%”开头的LIKE语句,模糊匹配
  • OR语句前后没有同时使用索引
  • 数据类型出现隐式转化(如varchar不加单引号的话可能会自动转换为int型)
  • 使用不等于查询
  • 列参与了数学运算或者函数
  • 当mysql分析全表扫描比使用索引快的时候不使用索引
  • 当使用联合索引,前面一个条件为范围查询,后面的即使符合最左前缀原则,也无法使用索引

覆盖索引

覆盖索引指的是在一次查询中,如果一个索引包含或者说覆盖所有需要查询的字段的值,我们就称之为覆盖索引,而不再需要回表查询。

B+树在满足聚簇索引和覆盖索引的时候不需要回表查询数据,什么是聚簇索引?

在B+树的索引中,叶子节点可能存储了当前的key值,也可能存储了当前的key值以及整行的数据,这就是聚簇索引和非聚簇索引. 在InnoDB中,只有主键索引是聚簇索引,如果没有主键,则挑选一个唯一键建立聚簇索引.如果没有唯一键,则隐式的生成一个键来建立聚簇索引.
当查询使用聚簇索引时,在对应的叶子节点,可以获取到整行数据,因此不用再次进行回表查询.

非聚簇索引一定会回表查询吗?

不一定,这涉及到查询语句所要求的字段是否全部命中了索引,如果全部命中了索引,那么就不必再进行回表查询.

索引下推

select * from user where name like '李%' and age=35 and male=0;
可以看到,无索引下推是,每次都需要回表查询select * 的数据,然后再过滤李%的数据;
而索引下推的是在先过滤好结果集,回表拿select * 的数据

事务的基本特性

事务基本特性ACID分别是: 原子性指的是一个事务中的操作要么全部成功,要么全部失败。 一致性指的是数据库总是从一个一致性的状态转换到另外一个一致性的状态。 隔离性指的是一个事务的修改在最终提交前,对其他事务是不可见的。 持久性指的是一旦事务提交,所做的修改就会永久保存到数据库中。

同时有多个事务在进行会怎么样

多事务的并发进行一般会造成以下几个问题:
脏读: A事务读取到了B事务未提交的内容,而B事务后面进行了回滚.
不可重复读: 当设置A事务只能读取B事务已经提交的部分,会造成在A事务内的两次查询,结果竟然不一样,因为在此期间B事务进行了提交操作. 幻读: A事务读取了一个范围的内容,而同时B事务在此期间插入了一条数据.造成"幻觉"

事务的隔离级别

read uncommit 读未提交,可能会读到其他事务未提交的数据,也叫做脏读。
read commit 读已提交,两次读取结果不一致,叫做不可重复读。
repeatable read 可重复复读,这是mysql的默认级别,就是每次读取结果都一样,但是有可能产生幻读。
serializable 串行,一般是不会使用的,他会给每一行读取的数据加锁,会导致大量超时和锁竞争的问题。

读未提交 READ UNCOMMITTED

  • 很少使用
  • 不能保证一致性
  • 脏读(dirty read) : 使用到从未被确认的数据(例如: 早期版本、回滚) 锁:
  • 以非锁定方式执行
  • 可能的问题: 脏读、幻读、不可重复读

读已提交: READ COMMITTED

  • 每次查询都会设置和读取自己的新快照。
  • 仅支持基于行的 bin-log
  • UPDATE 优化: 半一致读(semi-consistent read)
  • 不可重复读: 不加锁的情况下, 其他事务 UPDATE 或 DELETE 会对查询结果有影响
  • 幻读(Phantom): 加锁后, 不锁定间隙, 其他事务可以 INSERT。 锁:
  • 锁定索引记录, 而不锁定记录之间的间隙
  • 可能的问题: 幻读、不可重复读

可重复读: REPEATABLE READ

  • InnoDB 的默认隔离级别
  • 使用事务第一次读取时创建的快照
  • 多版本技术 锁:
  • 使用唯一索引的唯一查询条件时, 只锁定查找到的索引记录, 不锁定间隙。
  • 其他查询条件, 会锁定扫描到的索引范围, 通过间隙锁或临键锁来阻止其他会话在这个范围中插入值。
  • 可能的问题: InnoDB 不能保证没有幻读, 需要加锁

串行化: SERIALIZABLE

最严格的级别,事务串行执行,资源消耗最大; 问题回顾:

  • 脏读(dirty read) : 使用到从未被确认的数据(例如: 早期版本、回滚)
  • 不可重复读: 不加锁的情况下, 其他事务 update 或 delete 会对结果集有影响
  • 幻读(Phantom): 加锁之后, 相同的查询语句, 在不同的时间点执行时, 产生不同的结果集 怎么解决?
    提高隔离级别、使用间隙锁或next-key锁

undo log: 撤销日志

  • 保证事务的原子性
  • 用处: 事务回滚, 一致性读、崩溃恢复
  • 记录事务回滚时所需的撤消操作
  • 一条 INSERT 语句,对应一条 DELETE 的 undo log
  • 每个 UPDATE 语句,对应一条相反 UPDATE 的 undo log 保存位置:
  • system tablespace (MySQL 5.7默认)
  • undo tablespaces (MySQL 8.0默认)
    回滚段(rollback segment)

redo log: 重做日志

  • 确保事务的持久性,防止事务提交后数据未刷新到磁盘就掉电或崩溃。
  • 事务执行过程中写入 redo log,记录事务对数据页做了哪些修改。
  • 提升性能: WAL(Write-Ahead Logging) 技术, 先写日志, 再写磁盘。
  • 日志文件: ib_logfile0, ib_logfile1
  • 日志缓冲: innodb_log_buffer_size
  • 强刷: fsync()

那ACID靠什么保证的呢?

A原子性由undo log日志保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sql;
C一致性一般由代码层面来保证;
I隔离性由MVCC来保证;
D持久性由内存+redo log来保证,mysql修改数据同时在内存和redo log记录这次操作,事务提交的时候通过redo log刷盘,宕机的时候可以从redo log恢复。

什么是幻读

什么是MVCC?

MVCC: 多版本并发控制

  • 使 InnoDB 支持一致性读: READ COMMITTED 和 REPEATABLE READ 。
  • 让查询不被阻塞、无需等待被其他事务持有的锁,这种技术手段可以增加并发性能。
  • InnoDB 保留被修改行的旧版本。
  • 查询正在被其他事务更新的数据时,会读取更新之前的版本。
  • 每行数据都存在一个版本号, 每次更新时都更新该版本
  • 这种技术在数据库领域的使用并不普遍。 某些数据库, 以及某些 MySQL 存储引擎都不支持。 聚簇索引的更新 = 替换更新
    二级索引的更新 = 删除+新建

MVCC 实现机制

  • 隐藏列
  • 事务链表, 保存还未提交的事务,事务提交则会从链表中摘除
  • Read view: 每个 SQL 一个, 包括 rw_trx_ids, low_limit_id, up_limit_id, low_limit_no 等
  • 回滚段: 通过 undo log 动态构建旧版本数据

InnoDB有三种行锁的算法:

Record Lock:

单个行记录上的锁。

Gap Lock:

间隙锁,左开右闭.锁定一个范围,但不包括记录本身。GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况。间隙锁是可重复读级别下才会有的锁,结合MVCC和间隙锁可以解决幻读的问题。

Next-Key Lock:

记录锁+间隙锁的组合
锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。左开右闭 && 左闭右开

死锁

产生死锁的必要条件:

互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
循环等待条件:在发生死锁时,必然存在一个进程--资源的环形链。

预防死锁

资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请求保持条件)
可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏循环等待条件)

一张表,里面有ID自增主键,当insert了10条记录之后,删除了第8,9,10条记录,再把Mysql重启,再insert一条记录,这条记录的ID是7还是11 ?

如果表的类型是MyISAM,那么是11。 因为MyISAM表会把自增主键的最大ID记录到数据文件里,重启MySQL自增主键的最大ID也不会丢失。 如果表的类型是InnoDB,那么是7。 InnoDB表只是把自增主键的最大ID记录到内存中,所以重启数据库或者是对表进行OPTIMIZE操作,都会导致最大ID丢失。

如何进行SQL优化?

  • 选择正确的存储引擎. 以 MySQL为例,包括有两个存储引擎 MyISAM 和 InnoDB,每个引擎都有利有弊。MyISAM 适合于一些需要大量查询的应用,但其对于有大量写操作并不是很好。甚至你只是需要update一个字段,整个表都会被锁起来,而别的进程,就算是读进程都无法操作直到读操作完成。另外,MyISAM 对于 SELECT COUNT(*) 这类的计算是超快无比的。InnoDB 的趋势会是一个非常复杂的存储引擎,对于一些小的应用,它会比 MyISAM 还慢。但是它支持“行锁” ,于是在写操作比较多的时候,会更优秀。并且,他还支持更多的高级应用,比如:事务。
  • 优化字段的数据类型 记住一个原则,越小的列会越快。如果一个表只会有几列罢了(比如说字典表,配置表),那么,我们就没有理由使用 INT 来做主键,使用 MEDIUMINT, SMALLINT 或是更小的 TINYINT 会更经济一些。如果你不需要记录时间,使用 DATE 要比 DATETIME 好得多。当然,你也需要留够足够的扩展空间。
  • 为搜索字段添加索引 索引并不一定就是给主键或是唯一的字段。如果在你的表中,有某个字段你总要会经常用来做搜索,那么最好是为其建立索引,除非你要搜索的字段是大的文本字段,那应该建立全文索引。
  • 避免使用Select 从数据库里读出越多的数据,那么查询就会变得越慢。并且,如果你的数据库服务器和WEB服务器是两台独立的服务器的话,这还会增加网络传输的负载。即使你要查询数据表的所有字段,也尽量不要用通配符,善用内置提供的字段排除定义也许能给带来更多的- - 使用 ENUM 而不是 VARCHAR ENUM 类型是非常快和紧凑的。在实际上,其保存的是 TINYINT,但其外表上显示为字符串。这样一来,用这个字段来做一些选项列表变得相当的完美。例如,性别、民族、部门和状态之类的这些字段的取值是有限而且固定的,那么,你应该使用 ENUM 而不是 VARCHAR。
  • 尽可能的使用 NOT NULL 除非你有一个很特别的原因去使用 NULL 值,你应该总是让你的字段保持 NOT NULL。 NULL其实需要额外的空间,并且,在你进行比较的时候,你的程序会更复杂。 当然,这里并不是说你就不能使用NULL了,现实情况是很复杂的,依然会有些情况下,你需要使用NULL值。
  • 固定长度的表会更快 如果表中的所有字段都是“固定长度”的,整个表会被认为是 “static” 或 “fixed-length”。 例如,表中没有如下类型的字段: VARCHAR,TEXT,BLOB。只要你包括了其中一个这些字段,那么这个表就不是“固定长度静态表”了,这样,MySQL 引擎会用另一种方法来处理。固定长度的表会提高性能,因为MySQL搜寻得会更快一些,因为这些固定的长度是很容易计算下一个数据的偏移量的,所以读取的自然也会很快。而如果字段不是定长的,那么,每一次要找下一条的话,需要程序找到主键。并且,固定长度的表也更容易被缓存和重建。不过,唯一的副作用是,固定长度的字段会浪费一些空间,因为定长的字段无论你用不用,他都是要分配那么多的空间。

如何设计一个高并发的系统

  • 数据库的优化,包括合理的事务隔离级别、SQL语句优化、索引的优化
  • 使用缓存,尽量减少数据库 IO
  • 分布式数据库、分布式缓存
  • 服务器的负载均衡

锁的优化策略

  • 读写分离
  • 分段加锁
  • 减少锁持有的时间
  • 多个线程尽量以相同的顺序去获取资源 等等,这些都不是绝对原则,都要根据情况,比如不能将锁的粒度过于细化,不然可能会出现线程的加锁和释放次数过多,反而效率不如一次加一把大锁。

SQL语句优化有哪些方法

  • Where子句中:where表之间的连接必须写在其他Where条件之前,那些可以过滤掉最大数量记录的条件必须写在Where子句的末尾.HAVING最后。
  • 用EXISTS替代IN、用NOT EXISTS替代NOT IN。
  • 避免在索引列上使用计算
  • 避免在索引列上使用IS NULL和IS NOT NULLmysql最新面试题
  • 对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
  • 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
  • 应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描

联合索引是什么?为什么需要注意联合索引中的顺序?

MySQL中用多个字段同时建立一个索引,叫做联合索引.在联合索引中,如果想要命中索引,需要按照建立索引时的字段顺序挨个使用,否则无法命中索引。
MySQL使用索引时需要索引有序,因此在建立联合索引的时候应该注意索引列的顺序,一般情况下,将查询需求频繁或者字段选择性高的列放在前面.此外可以根据特例的查询或者表结构进行单独的调整。

单机 MySQL 数据库的几个问题

随着数据量的增大,读写并发的增加,系统可用性要求的提升,单机 MySQL 面临:
1、容量有限,难以扩容
2、读写压力,QPS 过大,特别是分析类需求会影响到业务事务
3、可用性不足,宕机问题

binlog格式

  1. ROW
  2. Statement
  3. Mixed

分库分表

首先分库分表分为垂直和水平两个方式,一般来说我们拆分的顺序是先垂直后水平。

分表后的ID怎么保证唯一性的呢?

因为我们主键默认都是自增的,那么分表之后的主键在不同表就肯定会有冲突了。有几个办法考虑:
设定步长,比如1-1024张表我们分别设定1-1024的基础步长,这样主键落到不同的表就不会冲突了。
分布式ID,自己实现一套分布式ID生成算法或者使用开源的比如雪花算法这种
分表后不使用主键作为查询依据,而是每张表单独新增一个字段作为唯一主键使用,比如订单表订单号是唯一的,不管最终落在哪张表都基于订单号作为查询依据,更新也一样。

  • 自增
  • sequence
  • 模拟 seq
  • UUID
  • 时间戳/随机数
  • snowflake

分表后非sharding_key的查询怎么处理呢?

  • 可以做一个mapping表,比如这时候商家要查询订单列表怎么办呢?不带user_id查询的话你总不能扫全表吧?所以我们可以做一个映射关系表,保存商家和用户的关系,查询的时候先通过商家查询到用户列表,再通过user_id去查询。
  • 大宽表,一般而言,商户端对数据实时性要求并不是很高,比如查询订单列表,可以把订单表同步到离线(实时)数仓,再基于数仓去做成一张宽表,再基于其他如es提供查询服务。
  • 数据量不是很大的话,比如后台的一些查询之类的,也可以通过多线程扫表,然后再聚合结果的方式来做。或者异步的形式也是可以的。

MySQL主从复制原理

  1. master提交完事务后,写入binlog
  2. slave连接到master,获取binlog
  3. master创建dump线程,推送binglog到slave
  4. slave启动一个IO线程读取同步过来的master的binlog,记录到relay log中继日志中
  5. slave再开启一个sql线程读取relay log事件并在slave执行,完成同步
  6. slave记录自己的binglog

由于mysql默认的复制方式是异步的,主库把日志发送给从库后不关心从库是否已经处理,这样会产生一个问题就是假设主库挂了,从库处理失败了,这时候从库升为主库后,日志就丢失了。由此产生两个概念。

  • 全同步复制 主库写入binlog后强制同步日志到从库,所有的从库都执行完成后才返回给客户端,但是很显然这个方式的话性能会受到严重影响。
  • 半同步复制 和全同步不同的是,半同步复制的逻辑是这样,从库写入日志成功后返回ACK确认给主库,主库收到至少一个从库的确认就认为写操作完成。

主从复制的局限性

  1. 主从延迟问题
  2. 应用侧需要配合读写分离框架
  3. 没有解决高可用问题

MySQL读写分离

动态切换数据源版本1.0

1、基于 Spring/Spring Boot,配置多个数据源(例如2个,master 和 slave)
2、根据具体的 Service 方法是否会操作数据,注入不同的数据源,1.0版本
3、改进一下1.1:基于操作 AbstractRoutingDataSource 和自定义注解 readOnly之类的,简化自动切换数据源
4、改进二下1.2:支持配置多个从库;
5、改进三下1.3:支持多个从库的负载均衡。
缺点:
1)侵入性还是较强 2)降低侵入性会导致”写完读”不一致问题

数据库框架版本2.0

ShardingSphere-jdbc 的 Master-Slave 功能 1)SQL 解析和事务管理,自动实现读写分离
2)解决”写完读”不一致的问题
缺点:
1)对业务系统还是有侵入
2)对已存在的旧系统改造不友好

读写分离-数据库中间件版本3.0

MyCat/ShardingSphere-Proxy 的 Master-Slave 功能 1)需要部署一个中间件,规则配置在中间件 2)模拟一个 MySQL 服务器,对业务系统无侵入

MySQL高可用

为什么需要高可用?

1、读写分离,提升读的处理能力
2、故障转移,提供 failover 能力
加上业务侧连接池的心跳重试,实现断线重连,业务不间断,降低 RTO 和 RPO。

什么是 failover,故障转移,灾难恢复
容灾:热备与冷备
对于主从来说,简单讲就是主挂了,某一个从,变成主, 整个集群来看,正常对外提供服务 常见的一些策略:
1、多个实例不在一个主机/机架上
2、跨机房和可用区部署
3、两地三中心容灾高可用方案

高可用定义

高可用意味着,更少的不可服务时间。一般用SLA/SLO衡量。
1年 = 365天 = 8760小时
99 = 8760 * 1% = 8760 * 0.01 = 87.6小时
99.9 = 8760 * 0.1% = 8760 * 0.001 = 8.76小时
99.99 = 8760 * 0.0001 = 0.876小时 = 0.876 * 60 = 52.6分钟
99.999 = 8760 * 0.00001 = 0.0876小时 = 0.0876 * 60 = 5.26分钟

MySQL 高可用0:主从手动切换

如果主节点挂掉,将某个从改成主;
重新配置其他从节点。
修改应用数据源配置。
缺点:

  1. 可能数据不一致。
  2. 需要人工干预。
  3. 代码和配置的侵入性。

MySQL 高可用1:主从手动切换

用 LVS+Keepalived 实现多个节点的探活+请求路由。
配置 VIP 或 DNS 实现配置不变更。 缺点:

  1. 手工处理主从切换
  2. 大量的配置和脚本定义

MySQL 高可用2:MHA

MHA(Master High Availability)目前在 MySQL 高可用方面是一个相对成熟的解决方案,它由日本 DeNA 公司的 youshimaton(现就职于 Facebook 公司)开发,是一套优秀的作为 MySQL 高可用性环境下故障切换和主从提升的高可用软件。 基于 Perl 语言开发,一般能在30s内实现主从切换。
切换时,直接通过 SSH 复制主节点的日志。
缺点:

  1. 需要配置 SSH 信息
  2. 至少3台

MySQL 高可用3:MGR *

如果主节点挂掉,将自动选择某个从改成主;
无需人工干预,基于组复制,保证数据一致性。
缺点:

  1. 外部获得状态变更需要读取数据库。
  2. 外部需要使用 LVS/VIP 配置。

MGR: MySQL Group Replication

高一致性: 基于分布式Paxos协议实现组复制,保证数据一致性;
高容错性: 自动检测机制,只要不是大多数节点都宕机就可以继续工作,内置防脑裂保护机制; 高扩展性: 节点的增加和移除会自动更新组成员信息,新节点加入后,自动从其他节点同步增量数据,直到与其他节点数据一致; 高灵活性: 提供单主模式和多主模式,单主模式在主库宕机后能够自动选主,所有写入都在主节点进行,多主模式支持多节点写入;

MGR使用场景

弹性复制;
高可用分片;

MySQL 高可用4:MySQL Cluster

完整的数据库层高可用解决方案。
MySQL InnoDB CLuster是一个高可用的框架,主要组成部分如下:

  1. MySQL Group Replication:提供DB的扩展、自动故障转移等;
  2. MySQL Router: 轻量级中间件,提供应用程序连接目标的故障转移;
  3. MySQL Shell: 新的MySQL客户端,多种接口模式。可以设置群组复制和Router。

MySQL 高可用5:Orchestrator

如果主节点挂掉,将某个从改成主; 特点:

  1. 自动发现MySQL的复制拓扑,并且在Web上展示;
  2. 重构复制关系,可以在web进行拖图来进行复制关系变更;
  3. 检测主异常,并可以自动或者手动恢复,通过hooks进行自定义脚本;
  4. 支持命令行和web界面管理复制;

为什么要做数据库拆分

业务飞速发展导致了数据规模的急速膨胀,单机数据库已经无法适应互联网业务的发展;传统的将数据集中存储至单一数据节点的解决方案,在容量、性能、可用性和运维成本这三方面已经难于满足互联网的海量数据场景。我们在单库单表数据量超过一定容量水位的情况下,索引树层级增加,磁盘IO也很可能出现压力,会导致很多问题。
从性能方面来说,由于关系型数据库大多采用 B+树类型的索引,在数据量超过阈值的情况下,索引深度的增加也将使得磁盘访问的 IO 次数增加,进而导致查询性能的下降;同时,高并发访问请求也使得集中式数据库成为系统的最大瓶颈。
从可用性的方面来讲,服务化的无状态型,能够达到较小成本的随意扩容,这必然导致系统的最终压力都落在数据库之上。而单一的数据节点,或者简单的主从架构,已经越来越难以承担。从运维成本方面考虑,当一个数据库实例中的数据达到阈值以上,数据备份和恢复的时间成本都将随着数据量的大小而愈发不可控。

单库可能出现的问题

1、无法执行DDL,比如添加一列,或者增加索引,都会直接影响线上业务,导致长时间的数据库无响应;
2、无法备份,与上面类似,备份会自动先 lock 数据库的所有表,然后导出数据,量大了就没法执行了;
3、影响性能与稳定性,系统越来越慢,随时可能会出现主库延迟高,主从延迟很高,且不可控,对业务系统有极大的破坏性影响;

从读写分离到数据库拆分

主从结构解决了高可用,读扩展,但是单机容量不变,单机写性能无法解决。
提升容量-->分库分表,分布式,多个数据库,作为数据分片的集群提供服务。
降低单个节点的写压力。
提升整个系统的数据容量上限。

解决思路

  • 通过 clone 整个系统复制,集群 --- 数据复制 --- 主从结构、备份与高可用
  • 通过解耦不同功能复制,业务拆分 --- 垂直分库分表 --- 分布式服务化、微服务
  • 通过拆分不同数据扩展,数据分片 --- 水平分库分表 --- 分布式结构、任意扩容

垂直拆分

垂直分库分表 => 分布式服务化 => 微服务架构

拆库

垂直拆分(拆库):将一个数据库,拆分成多个提供不同业务数据处理能力的数据库。
例如拆分所有订单的数据和产品的数据,变成两个独立的库,这种方式对业务系统有极大的影响,因为数据结构本身发生了变化,SQL 和关联关系也必随之发生了改变。原来一个复杂 SQL 直接把一批订单和相关的产品都查了出来,现在这个 SQL 不能用了,得改写 SQL 和程序。先查询订单库数据,拿到这批订单对应的所有产品 id,再根据产品 id 集合去产品库查询所有的产品信息,最后再业务代码里进行组装。

拆表

垂直拆分(拆表):如果单表数据量过大,还可能需要对单表进行拆分。
比如一个 200 列的订单主表,拆分成十几个子表:订单表、订单详情表、订单收件信息表、订单支付表、订单产品快照表等等。这个对业务系统的影响有时候可能会大到跟新做一个系统差不多。对于一个高并发的线上生产系统进行改造,动的愈多,越核 心,出现大故障的风险越高。所以,我们一般情况下,尽量少用这种办法。

垂直拆分的优缺点

  1. 单库(单表)变小,便于管理和维护
  2. 对性能和容量有提升作用
  3. 改造后,系统和数据复杂度降低
  4. 可以作为微服务改造的基础
  5. 库变多,管理变复杂
  6. 对业务系统有较强的侵入性
  7. 改造过程复杂,容易出故障
  8. 拆分到一定程度就无法继续拆分

垂直拆分的一般做法

  1. 梳理清楚拆分范围和影响范围
  2. 检查评估和重新影响到的服务
  3. 准备新的数据库集群复制数据
  4. 修改系统配置并发布新版上线
    注意:
  5. 先拆分系统,还是先拆分数据库?
  6. 先拆分多大范围?

数据库水平拆分

什么是水平拆分?

水平分库分表分为:
分库、分表、分库分表三类

数据库水平拆分

水平拆分(按主键分库分表):水平拆分就是直接对数据进行分片,有分库和分表两个具体方式,但是都只是降低单个节点数据量,但不改变数据本身的结构。这样对业务系统本身的代码逻辑来说,就不需要做特别大的改动,甚至可以基于一些中间件做到透明。
比如把一个 10 亿条记录的订单单库单表(orderDB 库 t_order 表)。我们按照用户 id 除以32 取模,把单库拆分成 32 个库orderDB_00..31;再按订单 id 除以 32 取模,每个库里再拆分成 32 个表t_order_00..31。这样一共是 1024 个子表,单个表的数据量就只是 10 万条了。一个查询如果能够直接路由到某个具体的字表,比如orderDB05.t_order_10 ,那么查询效率就会高很多。

数据库水平拆分

水平拆分(按时间分库分表):很多时候,我们的数据是有时间属性的,所以自然可以按照时间维度来拆分。比如当前数据表和历史数据表,甚至按季度,按月,按天来划分不同的表。这样我们按照时间维度来查询数据时,就可以直接定位到当前的这个子表。更详细的分析参考下一个小节。
强制按条件指定分库分表:比如配置好某些用户的数据进入单独的库表,其他数据默认处理。
自定义方式分库分表:指定某些条件的数据进入到某些库或表。

分库还是分表,如何选择

一般情况下,如果数据本身的读写压力较大,磁盘 IO 已经成为瓶颈,那么分库比分表要好。分库将数据分散到不同的数据库实例,使用不同的磁盘,从而可以并行提升整个集群的并行数据处理能力。相反的情况下,可以尽量多考虑分表,降低单表的数据量,从而减少单表操作的时间,同时也能在单个数据库上使用并行操作多个表来增加处理能力。

数据库水平拆分

分库分表有什么优缺点:
1、解决容量问题
2、比垂直拆分对系统影响小
3、部分提升性能和稳定性
1、集群规模大,管理复杂
2、复杂 SQL 支持问题(业务侵入性、性能)
3、数据迁移问题
4、一致性问题

数据的分类管理

通过分类处理提升数据管理能力
随着我们对业务系统、对数据本身的进一步了解,我们就会发现,很多数据对质量的要求是不同的。
比如,订单数据,肯定一致性要求最高,不能丢数据。而日志数据和一些计算的中间数据,我们则是可以不要那么高的一致性,丢了不要了,或者从别的地方找回来。
同样地,我们对于同样一张表里的订单数据,也可以采用不同策略,无效订单如果比较多,我们可以定期的清除或者转移(一些交易系统里有 80%以上是的机器下单然后取消的无意义订单,没有人会去查询它,所以可以清理)。
如果没有无效订单,那么我们也可以考虑:

  1. 最近一周下单但是未支付的订单,被查询和支付的可能性较大,再长时间的订单,我们可以直接取消掉。
  2. 最近 3 个月下单的数据,被在线重复查询和系统统计的可能性最大。
  3. 超过 3 个月、3 年以内的数据,查询的可能性非常小,我们可以不提供在线查询。
  4. 3 年以上的数据,我们可以直接不提供任何方式的查询。
    这样的话,我们就可以采取一定的手段去优化系统:
  5. 定义一周内下单但未支付的数据为热数据,同时放到数据库和内存;
  6. 定义三个月内的数据为温数据,放到数据库,提供正常的查询操作;
  7. 定义 3 个月到 3 年的数据,为冷数据,从数据库删除,归档到一些便宜的磁盘,用压缩的方式(比如 MySQL 的 tokuDB 引擎,可以压缩到几十分之一)存储,用户需要邮件或者提交工单来查询,我们导出后发给用户;
  8. 定义 3 年以上的数据为冰数据,备份到磁带之类的介质上,不提供任何查询操作。
    我们可以看到,上面都是针对一些具体场景,来分析和给出解决办法。那么通过在各种不同的场景下,都对现有的技术和手段进行一些补充,我们就会逐渐得到一个复杂的技术体系。

分库分表框架和中间件

Java 框架层面:

  • TDDL
  • Apache ShardingSphere-JDBC
    中间件层面:
  • DRDS(商业闭源)
  • Apache ShardingSphere-Proxy
  • MyCat/DBLE
  • Cobar
  • Vitness
  • KingShard

数据库中间件 ShardingSphere

Apache ShardingSphere 是一套开源的分布式数据库中间件解决方案组成的生态圈,它由 JDBC、Proxy 和 Sidecar(规划中)这 3 款相互独立,却又能够混合部署配合使用的产品组成。 它们均提供标准化的数据分片、分布式事务和数据库治理功能,可适用于如 Java 同构、异构语言、云原生等各种多样化的应用场景。

框架 ShardingSphere-JDBC直接在业务代码使用。支持常见的数据库和 JDBC。Java only。

中间件 ShardingSphere-Proxy作为中间件,独立部署,对业务端透明。目前支持 MySQL 和 PostgreSQL。任何语言平台的系统都可以接入,可以使用 mysql 命令或者 IDE 操作。对业务系统侵入性小。

如何做数据迁移

  • 设计新系统容易,但是我们处理的都是老系统和历史数据
  • 怎么能更平滑的迁移旧数据到新的数据库和系统
  • 特别是在异构的数据库结构情况下
  • 达到数据准确,迁移速度快,减少停机,对业务影响小

数据迁移的方式:全量

  • 全量数据导出和导入
  1. 业务系统停机,
  2. 数据库迁移,校验一致性,
  3. 然后业务系统升级,接入新数据库。 直接复制的话,可以 dump 后全量导入;如果是异构数据,需要用程序来处理

数据迁移的方式:全量+增量

  • 依赖于数据本身的时间戳
  1. 先同步数据到最近的某个时间戳
  2. 然后在发布升级时停机维护,
  3. 再同步最后一段时间(通常是一天)的变化数据。
  4. 最后升级业务系统,接入新数据库。

数据迁移的方式:binlog+全量+增量

  • 通过主库或者从库的 binlog 来解析和重新构造数据,实现复制。
  • 一般需要中间件等工具的支持。 可以实现多线程,断点续传,全量历史和增量数据同步。
    继而可以做到:
    1、实现自定义复杂异构数据结构;
    2、实现自动扩容和缩容,比如分库分表到单库单表,单库单表到分库分表,分4个库表到分64个库表。

数据库中间件 ShardingSphere

迁移工具 ShardingSphere-scaling

  • 支持数据全量和增量同步。
  • 支持断点续传和多线程数据同步。
  • 支持数据库异构复制和动态扩容。
  • 具有 UI 界面,可视化配置。

本文整理自网络