分类目录归档:程序相关

C,Python,环境配置等

锁机制之MySQL表锁

如何保证在被并发访问时数据的一致性、完整性和有效性,是数据库关注的核心问题。数据库的锁机制就是为了解决这个问题而出现的。锁机制在一定程度上将对共享资源的并发访问有序化,从而保证数据的一致完整性。锁机制的好坏直接影响到数据的并发处理能力和性能。一个好的锁机制的实现是一个数据的核心竞争力之一。

我们知道在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性能调优与架构设计》 – 简朝阳

在线修改MySQL大表的表结构

问题描述

由于某个临时需求,需要给在线MySQL的某个超过千万的表增加一个字段。此表在设计之时完全按照需求实现,并没有多余的保留字段。

我们知道在MySQL中如果要执行ALTER TABLE操作,MySQL会通过制作原来表的一个临时副本来工作。对于表结构的修改在副本上施行,然后将新表替换原始表,此时会产生锁表,用户可以从原始表读取数据,而用户的更新和写入操作都会被lock,待新表准备好后写入新表。
这对于在线的数据量较大的表来说是绝对无法容忍的,并且由于这种在线操作时间会很长,此时如果show processlist,会发现有若干的MySQL进程处于lock状态,当这种进程太多超过单台服务器允许的MySQL进程数,其它进程可能会被拒绝连接。

有哪些方案可以处理这个问题呢?

方案1、直接ALTER TABLE
这个方案只能说这仅仅是一种方案,在某些非实时在线或数据量较小时有较好的表现。

方案2、模拟数据库修改表结构的操作,在非数据库层实现整个过程。

  1. 实现业务中对于数据的读写分离
  2. 创建一个已经按需求修改好结构的新表
  3. 修改业务逻辑,将读操作指向旧表,将写操作指向新表。如果读旧表没有,再读新表,并将旧的数据写入到新表,当然这一步写入操作我们可以不用,我们可以在后台做一个定时任务将旧数据同步到新表。

这种方案有一个较大的缺点,需要业务逻辑层配合实现数据的迁移,对于业务逻辑有修改,并且如果有多台机器的话,需要一台一台的修改,较费时间,但是对于MySQL的两种主要存储引擎都适用。


方案3、facebook online schema change
facebook的OSC在整体流程上与方案2没有较大的区别,只是它在这里引入了触发器,从而不需要修改业务逻辑,在数据库层就实现了新数据的两个表的同步问题。其大概步骤如下:

  1. 按需求创建新表
  2. 针对原始表创建触发器
  3. 对于原始表的更新操作都会被触发器更新到新表中
  4. 把原始表中的数据复制到新表中
  5. 将新表替换旧表

fb的osc方案从数据库层解决了方案2的问题,但是它仅支持InnoDB存储引擎。


方案4、换一个思路,保留字段。
假设一切可以从头再来,我们也许可以加多一些冗余字段,各个类型都加一些,备用。只是,回不去了!

方案5、再换一个思路,增加扩展表。
我们不在原有的表的基础上修改了,以增加扩展表的方式,将新字段的数据写入到扩展表中,修改业务逻辑,这些字段从新表中读取。
志强同学说这是典型的维表结构设计。
暂时解决了问题,如果这些字段后续使用频率高的话,可能会有对后期维护或业务有一定的影响。

后记
基于现有的需求,只是需要记录新的字段,所以采用了扩展表的方案。

从PHP的自动测试想到的

从PHP的自动测试想到的

昨日,因TIPI项目而阅读了PHP的自动测试实现相关代码。于此,有些许感想,记录如下。

1、用自己测试自己,制定测试过程规范。
PHP的测试环境是用PHP实现的,这不得不说是一个创新之举。相对于编译型语言,作为动态语言的PHP在应对变化上有着不少的优势,而测试本来就是一个变化是非较多的地儿。其实用到了PHP的地方只是这个框架的控制器,即源码根目录下run-tests.php文件。作为控制器,它实现了整个测试过程的控制。以一个测试过程为例,总体上分为三个部分:准备、运行和显示结果。准备活动包括测试所必须的环境变量的读取与设置,对测试参数的解析,测试脚本名的解析,各种输出文件的准备 解析测试脚本中的各个段落等;运行活动包括构造测试语句,执行测试语句,得到实际运行结果;显示结果活动包括测试后的结果比对及输出,相关记录记录以及总的测试报告显示。

这个控制器就是PHP自动测试的规范,所有的逻辑都在这一个脚本文件中,在一个时间点上,这是一个不变的过程。对于测试中变化的内容如测试环境,测试输入数据、需要验证的内容以及针对不同输入和不同测试点应该得到的预期结果,这些都存储在PHPT文件中,以不同的标记作为段分开。这些文件按模块划分,一个用例就是一个文件,与将用例写成代码相比,优势不仅仅在于工作量,更多的是在于它的扩展性、可读性和可维护性。

2、简单监控框架

先确认我们这个监控框架的需求什么。现在我们要的是一个可以监控数据是否正常,数据的状态是否符合业务逻辑,并将监控的结果发给相关负责人。从这个简单的需求出发,我们可以发现这里变化的是监控的内容,而不变的是整个监控的流程:查询特定的数据源,根据具体业务确认数据的正确性和合理性,并将结果发送给相关责任人。

对于不变的因素,我们可以以公共模块的方式在代码中实现,如果汇报结果的形式有不同的分类和权限控制的话,我们可以将这些配置放到数据库,当然,我们还是需要在代码中实现这些汇报的方式。

对于变化的因素,我们可以学习PHP的测试过程,以某些特定的规则定义一个一个的监控,我们可以称之为监控用例。在用例中定义名称、输入、过程和预期结果。比如,我们可以定义–SQL–字段做数据源。当然,这些内容我们可以分散存储,也可以集中存储在数据库。

这样一种以测试的方式实现监控过程,也许可以试试。