优化
读写分离
一主多从,主库写,从库读,会进行数据同步
- 实现方式:
- 1、代理方式,应用和数据库之间加一个代理层,代理层负责区分读写请求并分发到不同的数据库
- 这种方式可以不用管上游使用的是什么语言
- 2、组件方式:如
sharding-jdbc - 3、手动指定,不同的 dao 使用不同的 db 实例
- 1、代理方式,应用和数据库之间加一个代理层,代理层负责区分读写请求并分发到不同的数据库
- 问题:如果写入后立刻读取,可能从库同步没完成,导致读取到旧数据,解决方案
- 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 是可以从引擎层获取的数据更少一些
- 先查询 limit 的较大值,在 limit,如
- 延迟关联
- 和子查询类似,不过使用 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
- 选择合适字段
- 设计合适索引
