日期:2014-05-16  浏览次数:20416 次

[转载]InnoDB行锁的实现分析

原文网址: http://www.penglixun.com/tech/database/innodb_next_key_locking.html

本文内容遵从CC版权协议, 可以随意转载, 但必须以超链接形式标明文章原始出处和作者信息及版权声明

?

感谢Fenng的提醒,已删除锁粒度和死锁的关系,专门撰文写了锁的粒度与死锁的关系

InnoDB与MyISAM不同,它实现的是一个行级锁,而非MyISAM的表锁。锁的粒度越大,则发生死锁的概率越小、锁机制开销越小,但并发能力会越低。如果锁的粒度变细,则发生死锁的概率也会增大,锁机制的开销会更大,但是并发能力能提高。表锁是如何实现的呢,以MyISAM为例,是在每个表的结构中加入一个互斥变量记录锁状态,像:
struct Table {
Row rows[MAXROWS];
pthread_mutex_t lock;//表锁
};
这样做的好处就是锁非常简单,当操作表的时候,直接锁住整个表就行,锁机制的开销非常小。但是问题也很明显,并发量上不去,因为无论多小的操作,都必须锁整个表,这可能带来其他操作的阻塞。
行锁又是如何实现的呢,Oracle是直接在每个行的block上做标记,而InnoDB则是靠索引来做。InnoDB的主键索引跟一般的索引不太一样,Key后面还跟上了整行的数据,互斥变量也是加载主键索引上的,像
struct PK_Idx {
Row row;
pthread_mutex_t lock;//行锁
};
multimap pk_idx;
这样的形式。
这样做的好处是锁的粒度小,只锁住需要的数据不被更改,但是问题也很明显,锁的开销很大,每个主键索引上都要加上一个标记,因为锁的粒度很小,可能两个不同的操作各锁住一部分行等待对方释放形成死锁,不过这个是有办法解决的,把上锁的操作封装成原子操作就行,不过并发量会受些影响。

下面是类似InnoDB的Next-Key locking算法的演示:
编译需要加-lpthread参数,例如g++ inno.cpp -lpthread -o inno

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <map>
#include <unistd.h>
#include <time.h>
#include <pthread.h>
#include <windows.h>
?
#define LOCK pthread_mutex_lock(&lock)
#define UNLOCK pthread_mutex_unlock(&lock)
#define PRINT(STR, ...) LOCK;fprintf(stderr, STR,  __VA_ARGS__);UNLOCK
?
#define MAXROWS 100
?
using namespace std;
?
/* 行结构 */
struct Row {
    int     num;
    string  info;
};
?
/* 主键索引结构 */
struct PK_Idx {
    Row     row;
    pthread_mutex_t lock;//行锁
};
?
/* 表结构 */
struct Table {
    multimap<int, PK_Idx> pk_idx;
    multimap<int, int>      num_idx;
    multimap<string, int>