如何保证在被并发访问时数据的一致性、完整性和有效性,是数据库关注的核心问题。数据库的锁机制就是为了解决这个问题而出现的。锁机制在一定程度上将对共享资源的并发访问有序化,从而保证数据的一致完整性。锁机制的好坏直接影响到数据的并发处理能力和性能。一个好的锁机制的实现是一个数据的核心竞争力之一。
我们知道在MySQL中存在表级锁、页级锁和行级锁,其中MySQL默认实现了表级锁定。其它锁机制在不同的存储引擎中实现,这也是MySQL特点之一:针对特定的应用场景可以使用当前合适的存储引擎。先不论各种存储引擎和锁机制的优劣,这里只是说说他们各自的特点和实现。
MyISAM存储引擎作为曾经的默认存储引擎,其使用的锁机制是MySQL提供的默认表级锁定。虽然它没有实现自己的锁机制,但是在默认表级锁的基础上,增加了并发插入的特性。并发插入与系统参数concurrent_insert相关,concurrent_insert有三个值:
- concurrent_insert=0 关闭并发写入
- concurrent_insert=1 (默认)在没有空数据块的MyISAM表中启用并行插入
- concurrent_insert=2 为所有MyISAM表启用并行插入。如果表有空记录或正被另一线程使用,新行将插入到表的最后。如果表未使用,MySQL将进行普通读锁定并将新行插入空记录。
此参数与MyISAM存储引擎的数据存储方式相关:常规情况下,MyISAM的新数据都会被附加到数据文件的结尾,当做了一些DELETE操作之后,数据文件就不再是连续的,形象一点来说,就是数据文件里出现了很多hole,此时再插入新数据时,按缺省设置会先看这些hole的大小是否可以容纳下新数据,如果可以,则直接把新数据保存到hole里,反之,则把新数据保存到数据文件的结尾。之所以这样做是为了减少数据文件的大小,降低文件碎片的产生。
如果我们使用concurrent_insert=2(通常也推荐这样做),这样会产生较多的文件碎片,为此,我们需要在设置这个参数值的同时,定期对数据表进行OPTIMIZE TABLE操作。此操作可以去除删除操作后留下的数据文件碎片,减小文件尺寸,加快未来的读写操作。但是,在OPTIMIZE TABLE运行过程中,MySQL会锁表。
MySQL的表锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)。共享锁和独占锁在锁机制中是一种非常普通的实现方式。 MyISAM在执行查询语句前,会自动给涉及的所有表加读锁,在执行更新操作(DDL)前,会自动给相关的表加写锁。 MySQL的读写锁(mysys/thr_lock.c)是通过4个队列来维护的,他们分别是:
- 当前读锁队列(lock->read): 存储当前持有读锁所有线程相关信息,按获取锁的时间排序
- 读锁等待队列(lock->read_wait):存储正在等待读锁锁定资源的线程相关信息
- 当前写锁队列(lock->write):存储当前持有写锁所有线程相关信息,按获取锁的时间排序
- 写锁等待队列(lock->write_wait):存储正在等待写锁锁定资源的线程相关信息
对于读锁,当请求的资源没有加写锁或在写锁等待队列中没有更高优先级的写锁定在等待。读锁是共享锁,不会阻塞其他进程对同一资源的读请求,但会阻塞对同一资源的写请求。只有当读锁释放后,才会执行其它进程的写操作。
对于写锁,当请求的资源在当前写锁队列、写锁等待队列或当前读锁队列,进入等待写锁队列;写锁会阻塞其他进程对同一资源的读和写操作,只有当写锁释放后,才会执行其它进程的读写操作。
表锁是MySQL数据库中加锁粒度最大的一种锁,除此之外,MySQL还有页级锁和行锁。表锁的执行开销小,加锁速度快,不会出现死锁,但是其加锁的粒度大,发生锁冲突的概率非常高,从而导致并发度低。可以考虑使用主从结构解决并发度低的问题。
参考资料
http://www.zhaokunyao.com/archives/206
http://dev.mysql.com/doc/refman/5.1/zh/database-administration.html
《MySQL性能调优与架构设计》 – 简朝阳