数据库表的轮转(续)

上次提到了数据库表的轮转,大致阐述了问题及初步的解决方法。然而实现的时候还是会遇到些问题。

轮转的时候,需要将旧表A中未完成的任务都迁移到新建的表B中(当然也可以不迁移,而是直接将旧表中的任务处理完为止,但这种方式更为复杂曲折,不予考虑)。迁移的时候有可能会往A中继续插入新的任务,迁移可能会遗漏一些记录;或者从A中取出任务执行完后需要更新任务状态时却发现A中该记录已被迁移到B中了,造成任务状态的不确定性;又或者在A中存在某用户U的开通记录但处理时失败了需要继续重试,但迁移过程中继续插入了一条取消记录,那么就有可能取消成功了但是又由于之前有个失败的开通记录,重试后就导致又被开通了。可能存在的问题还有许多,比如有可能新建表或迁移的时候数据库处理失败了。有没办法将创建新表、迁移记录处理成一个原子操作呢?

首先想到的是通过加锁。具体步骤如下:

  1. 创建新表B
  2. 对旧表A和新表B及轮转记录表C都加写锁(lock tables a write, b write, c write;)
  3. 关闭自动提交(set autocommit=0)
  4. 更新轮转记录C
  5. 将旧表A中需要迁移的记录都select出来,然后批量插入到B中
  6. 将sql语句中记录的tableName变量更改成新表B的表名
  7. 更新轮转记录C,标记本次迁移完成
  8. 释放表锁(unlock tables)及其他一些后续处理

但由于对数据库锁天然的畏惧,最终并没有使用该方式,而是通过自己程序内部控制以模拟达到锁的效果。具体步骤如下:

  1. 创建新表B
  2. 设置onCopy=true(onCopy=true时,其他希望更新任务记录的数据库操作都会阻塞在copyLock.wait()上)
    3~7与上述方式一致
  3. 设置onCopy=false,并调用copyLock.notifyAll()以唤醒所有阻塞的数据库操作

这次重构,由于对mysql的了解还不够深入,花费了许多时间,期间详细查看了mysql官方文档关于事务及锁的描述,获益匪浅