2011-5-25

我突然觉得,google只能帮助解决一些临时的、简单的技术问题,比如编译的时候报某个错、运行的时候某个错误消息,一搜,就解决了。但是稍微深一点的,就无奈了。离开了学校真是很郁闷,虽然在google 学术搜索中搜出好多我感兴趣的paper,但是下不了、下不了。所以,为了中国互联网事业的可持续发展,我一定要竭尽全力的勾搭各个高校的小学妹们,上门指导如何查询学术期刊,诱骗她们帮我下paper。嗯,就这么定了!

我又在想死锁的问题。secondary index引发deadlock是普遍现象,很多数据库实现中都遇到了此问题。例如mysql也有这样的问题。假设有一张表名为user,主键是userid,有一列是status。status上有索引,也就是说,它是这个表的secondary index、非聚簇索引。

userid(PK) status
1 0
100 2
10000000 1
9994454545 3

线程A执行查询"UPDATE user SET status=4 where status=1 ORDER BY userid LIMIT 1“,线程B执行查询"UPDATE user SET status=2 where userid=2;” 。Storage Engine是InnoDB,事务提交方式是autocommit。

原因很简单,就是和Berkeley DB一样的,到底是先访问Primary Key,还是先访问secondary index,顺序不同而导致死锁。

MySQL的slave一直是以单线程的方式执行SQL,这个目前看来已经是一个越来越大的瓶颈。首先是,主库是以多线程并行执行的,而从库却是单线程的,假设机器配置都一样,那么显然主库不能太busy,否则就会导致主从不同步。其次,因为binlog是顺序写入的,所以要求在主库上执行的所有事务必须存在可串行化的调度,而且是哪个事务先执行,就该哪个先完成。这就导致了update … set …. where xxxx的时候,应该把where中的条件所扫描过的所有行都用排它锁锁住,而不仅仅是最终匹配的那些行。以上面的表为例,假设status上没有索引,那么where status=3则会导致全表扫描并且锁全表,从而大大降低了并发。要解决这个问题,必须把事务的隔离度降低到read-committed,并且binlog格式改为row。但是我很好奇,默认是mixed,那么这种情况下,mysql又是如何选择的呢?

我在google上查了很久都没解决的问题就是关于read/write lock的模型的进一步分析。read/write lock很多种spec实现。不同的实现又对应着不同的死锁禁忌。

无论死锁检测机制做的多么完善,死锁都会严重增加程序的响应时间。最初我对死锁是完全不能忍的,在开发中把死锁检测关了。测试时一旦死锁就是整个hung掉,然后要求程序员必须立刻改掉。在上个项目做到最终上线半年以后,我渐渐的改变了看法。对于这么大型的一个事务系统,能做到完全的deadlock free,有点奇迹了。这里所说的deadlock free是指在每一块使用锁的地方都精心设计过了,从理论上来说都完全不会产生死锁。但是代价是,代码因此复杂了很多。我觉得归根结底,secondary index占了80%以上的原因。在面对一个新的架构,一个新的项目,如果对死锁表示容忍,但是又无法预计上线后死锁到底会有多么频繁,这是一件很冒险的事情。很难说到时候到底是需要大改,还是对小地方做做修修补补就行了。

对于产生死锁的原因,secondary index还不是最让我担心的,我最担心的是因为read lock upgrade to write lock而产生的死锁。这是一种很常见的现象。例如,大多数修改操作都是读出来、计算新值然后塞回去。读的时候拿的是读锁,塞回去的时候要upgrade成写锁。如果两个线程并发执行类似操作,那么就死锁了。而如果此处不是读写锁,是互斥锁,则完全没有这样的风险。Berkeley DB JE中似乎并没有select … for update这样的方式。

此博客中的热门博文

在windows下使用llvm+clang

少写代码,多读别人写的代码

tensorflow distributed runtime初窥