MySQL
01 Jan 2018变量
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