问:在一个业务系统有一张表,里面的数据已经过亿了,使得在业务查询的过程中就越来越慢,如何进行优化?
首先说一下分表方案的基本思路。在分表之前,需要对我们原有的表做一个数据观察(或者说数据分析),是否满足分表的特性,也就是要看表中属性是否有一些共性或者分布均匀的一些字段。这样就可以作为hash的一个路由基础。
同时还需要综合考虑对业务的影响。那么我们如何判断表中是否有共性或者分布均匀的一些字段?比如一张表中有用户ID,订单ID和商户ID等字段,结合实际的业务场景,查看用户ID吗,订单ID等那个查询的次数更多,根据具体的场景制定一个具体的分表路由。
再形象一些,假设这张表是一张用户的明细表,那么表中记录用户的入账和出账等交易流水,在这样的维度下,我们一般从用户的角度去分析。比如去查询用户的信息、交易记录等等,根据这个分析,我们假设要把这过亿的数据分为256张表,就是通过用户ID来做,使 用户ID的hash值%256
,用这种方式来判断用户记录的落表位置这,就使得同一个用户在同一张表上,从而简化业务逻辑的交互。
还有一些统计表是按时间维度来统计的,那么我们也可以使用年、月、日的形式来拆分表。
问:分表之后新数据写入新库,老数据也要迁移,那这个迁移如何实现?
一种效率较差的方案是停机迁移。在日常生活中见的比较多,也就是某些网站在凌晨0~6点进行系统维护,这就可能在做数据迁移,暂停整个网站的服务。这保证数据在迁移的过程中,没有新数据写入,这种模式如果在该过程中有新数据写入,可能就会导致数据丢失的情况。在停机之后,就运行早已写好的迁移脚本进行数据迁移,从单表中读出数据写入新的数据表中,同时修改数据库连接,最后重启项目。但是这种方式十分影响用户体验,同时也存在风险,比如一旦出现异常或者插入数据失败,此时由于时间比较紧迫,无法及时修复,使得整个迁移失败,比如出现停电或者服务器故障等等。
一种更加通用的方式是双写+同步迁移。它的实现思路是,在所有的主库中,只要是产生写入操作的地方,都进行双写处理。也就是说,新的数据不仅要写入到新表中,也要写入到老表中。而新表写入就用新的规则进行,查询正常还是查的是老表,当程序运行一段时间之后,如果发现新写入的数据都正常,就可以开始执行脚本,对老数据进行迁移,最后就是对数据的校验。这样我们的服务就不需要停机了。
实现双写的方式一般有两种,一种是使用MyBatis拦截器,去拦截修改操作,在拦截器中增加按照新表规则去修改,这样就不要去修改原来的代码了。另一种是通过canel+MQ 通过订阅的方式做数据同步。Canel可以理解为伪装成一个从数据库,只要主数据库有数据变更,Canel就会拿那条变更的SQL语句,然后操作的数据就会同步到比如kafka或者MQ中。然后再写一个程序去MQ上消费,来完成双写操作。老数据迁移还是使用脚本的方式,但是值得注意的是,不要一次性去跑完所有的数据,在开启双写之前,去记录双写开始的时间点,然后从这个时间点开始慢慢去分批去迁移,考虑服务的高峰期,错峰迁移。
最后,完成迁移之后,需要做一个切库切表的开关,把老表的访问切断。然后直接去访问新的数据库。这样对整个系统的性能提升。