Skip to content

优化

读写分离

一主多从,主库写,从库读,会进行数据同步

  • 实现方式:
    • 1、代理方式,应用和数据库之间加一个代理层,代理层负责区分读写请求并分发到不同的数据库
      • 这种方式可以不用管上游使用的是什么语言
    • 2、组件方式:如sharding-jdbc
    • 3、手动指定,不同的 dao 使用不同的 db 实例
  • 问题:如果写入后立刻读取,可能从库同步没完成,导致读取到旧数据,解决方案
    • 1、强制将读请求路由到主库(使用较多,会增加主库压力)
    • 2、延迟请求,等待一段时间再读取

分库分表

  • 分库:将数据库中的数据分散到不同的数据库上
    • 垂直分库:将单一数据库的不同表拆分到不同的数据库,分担单一数据库压力
    • 水平分库:将同一个表按照规则拆分到不同的数据库
  • 分表:对单表的数据进行拆分
    • 垂直分表:对数据表列的拆分,把一张列比较多的表拆分为多张表
    • 水平分表:对数据表行的拆分,把一张行比较多的表拆分为多张表
  • 分库分表比较重叠,只是一个拆分后还在同一个数据库,一个拆分后在不同的数据库
  • 场景:数据量过大或者并发量过大
  • 分片算法:
    • 哈希分片:哈希运算后取余,可以用一致性哈希(避免直接取余出现迁移难点)
    • 范围分片:特定范围内放在同一张表,适合备份
    • 地理位置分片:不同地域的数据放在一张表,如快递
    • 也可以融合多种
  • 分库成本较高,很多时候尽量不会拆用,分表成本较低,分库分表后:
    • 无法使用 join,需要业务层多次查询
    • 事务问题,需要使用分布式事务
    • 需要使用分布式 ID
    • 对于group by,order by等聚合查询成本较高
  • 开源项目:ShardingSphere
  • 现在一般使用 TiDB 这种分布式关系数据库替换分库分表
  • 迁移方式一般使用异步+双写,类似于 mysql 主从备份的方式

读扩散

  • 如果使用主键作为分片键,但是查询并没有通过主键进行过滤,那么就需要每一个分表都执行 sql 查询(无法直接确定在哪个分片上)
  • 解决方案:
    • 1、对于特定的非主键索引也做分片表,先通过这个分片表查询到主键 id,再到特定主分片表查询数据,维护成本较高
    • 2、es,通过倒排索引获取 id,可以通过 canal 监听 mysql 的 binlog 写入 es保证数据的实时性
    • 3、tidb:天然支持分片,普通索引也支持分片

数据冷热分离

根据数据的访问频率和重要性区分成冷数据和热数据,分别放在低性能和高性能存储介质中

  • 可以根据
    • 时间维度:创建时间等,将比较久的数据看作冷数据
    • 访问频率:一些浏览量比较低的数据(如文章)看作冷数据
  • 可以使用 TiDB,自带冷热分离,一般直接使用 mysql/pg 或者使用 Hbase 存储冷数据

深度分页

查询偏移量过大的场景称为深度分页,会导致查询性能较低 如 select id from t limit 10000,100

  • 因为 mysql 会获取 (offset+1) * limit 行记录,并将前面的 offset * limit 条记录丢弃,只取最后的 limit 数量的数据
    • 因为 mysql 只是保证有序,但是不能保证连续,不能直接通过偏移量确定数据在哪一页上
    • 并且如果是 select * ,还会将每一条完整的记录都读取
    • 如果回表,会更严重

优化方式

  • 如果 ID 连续,通过记录上次查询结果的最后一条记录 ID 进行下一页查询
    • 可以使用索引
    • 比如在产品上,只有上一页、下一页,而没有直接通过页码跳转,就可以使用这个(如视频、文章的瀑布流)
  • 子查询
    • 先查询 limit 的较大值,在 limit,如 SELECT * FROM t WHERE id >= (SELECT id FROM t_order limit 1000000, 1) LIMIT 100;
    • 这里子查询只查 id 是可以从引擎层获取的数据更少一些
  • 延迟关联
    • 和子查询类似,不过使用 join
sql
SELECT t1.* FROM t_order t1
INNER JOIN (SELECT id FROM t_order limit 1000000, 10) t2
ON t1.id = t2.id;

表设计优化

  • 字段 NOT NULL
  • 选择合适字段
  • 设计合适索引

正在精进