程序人生 A log of my life

MySQL

变量

mysql有user variables和system variables之分,前者以@为前缀,并且可以自己定义,后者不可以,system variables按作用域又可以分为session(或称为local)和global,参考下图:

session的生命周期就是connection,connection断开就消失了。所以如果要设置跨connection的变量,要用global,缺省的set和show variables命令都是session级别,如果要针对global生效,记得加上global参数。

事务隔离

MySQL如果使用事务,常用Innodb引擎,Innodb的事务缺省隔离级别是REPEATABLE_READ,这个意思是可重复的读,也就是在事务中,任何读取操作都是可重复的,不会因为其他事务的提交而发生变化。同时还包括了几个其他意思:

  • 不会读取其他未提交事务的数据(脏数据)
  • 事务里的第一个读取语句会建立整个数据库的快照(snapshot),后续的读取都基于这个快照。

上面的第二条非常重要,意味这个如果两个事务同时开启,A事务和B事务,如果B事务修改了数据并且提交,A能否在事务中读取到这个数据,取决于A事务是否已经执行了一条select语句,如果有select执行,则读取不到B事务提交的修改数据,否则可以读到,这个特性对并发设计非常重要。

并发

基于上面的缺省事务隔离机制,在并发时,很多应用并不是缺省缺省就高枕无忧了,比如最经典的场景,用户扣款,如果两个线程同时触发扣款,考虑下面的流程,最终的结果就是错误的:

  • A事务开始
  • A事务读取余额
  • B事务开始
  • B事务读取余额
  • A事务校验余额,并扣款保存
  • A事务提交
  • B事务校验余额,并扣款保存
  • B事务提交

最终的结果是,扣款只扣了一次,要解决这个问题有很多方法,我比较推荐的方法是,在读取余额时使用 for update限定,这样上述流程中B事务读取余额会被锁定,直到A提交完毕,并且加了for update之后,B事务可以读取到最新的修改后的数据,不受快照的约束,也就是整个流程会变成:

  • A事务开始
  • A事务读取余额
  • B事务开始
  • A事务校验余额,并扣款保存
  • A事务提交
  • B事务读取余额
  • B事务校验余额,并扣款保存
  • B事务提交

Join子表最新的一条

一次查出父表和子表里的最新一条,两次Join,第一次INNER JOIN找出所有子,第二次LEFT JOIN,做了一个N平方展开,其中为空的那条就是最新的一条,搞定但效率不高。

 SELECT parent.*, t1.*
  FROM parent
  INNER JOIN child AS t1
  ON (parent.id = child.parent_id)
  LEFT JOIN child AS t2
  ON (t1.parent_id = t2.parent_id and t1.datestamp < t2.datestamp)
  WHERE t2.datestamp IS NULL