pear安装

说到安装pear,我是因为给一个变量起一个名字,联想到了如何起名才规范,于是又联想到了PHPDocument,要安装PHPDocument可以通过pear安装和手动下载安装,于是乎我就折腾起了这个pear的安装。

根据:http://pear.php.net/manual/en/installation.getting.php中的内容翻译得来

点此http://pear.php.net/go-pear.phar下载文件到本地并命名为go-pear.phar进行保存。在cmd中输入php go-pear.phar 开始安装。

Are you installing a system-wide PEAR or a local copy?
(system|local) [system] :

 选择系统级别安装还是安装本地,默认是system,直接按回车继续。

Below is a suggested file layout for your new PEAR installation.  To
change individual locations, type the number in front of the
directory.  Type 'all' to change all of them or simply press Enter to
accept these locations.

 1. Installation base ($prefix)                   : D:\php5
 2. Temporary directory for processing            : D:\php5\tmp
 3. Temporary directory for downloads             : D:\php5\tmp
 4. Binaries directory                            : D:\php5
 5. PHP code directory ($php_dir)                 : D:\php5\pear
 6. Documentation directory                       : D:\php5\docs
 7. Data directory                                : D:\php5\data
 8. User-modifiable configuration files directory : D:\php5\cfg
 9. Public Web Files directory                    : D:\php5\www
10. Tests directory                               : D:\php5\tests
11. Name of configuration file                    : C:\Windows\pear.ini
12. Path to CLI php.exe                           : D:\php5

1-12, 'all' or Enter to continue:

以上是默认的pear的临时、数据、配置、测试、执行目录的设置,第11项默认的话会显示错误,我所以还是改到了D:\php5目录下(输入需要改的数字项,会提示让你输入新的目录地址,不改的话啥也不用输入直接回车)。然后回车。

然后一系列安装,其中有以下这个警告:

WARNING!  The include_path defined in the currently used php.ini does not
contain the PEAR PHP directory you just specified:
<D:\php5\pear>
If the specified directory is also not in the include_path used by
your scripts, you will have problems getting any PEAR packages working.

Would you like to alter php.ini<D:\php5\php.ini>?[Y/n]

需要将pear配置目录D:\php5\pear加入php.ini的include_path指令中。输入Y后自动修改php.ini中的路径。

php.ini <D:\php5\php.ini> include_path updated.

Current include path           : .;C:\php\pear
Configured directory           : D:\php5\pear
Currently used php.ini (guess) : D:\php5\php.ini
Press Enter to continue:

 到这里也没啥了,按回车基本安装成功了。会在D:\php5下面生成一个PEAR_ENV.reg的文件,双击运行进行注册表注册即可。

通过命令pear install package安装程序包,如果出现failed to mkdir的错误,只要以管理员身份重新运行cmd即可。

标签: PHP, pear

HTTP协议是如何工作的

浏览网页时HTTP协议的主要应用,但是这并不代表HTTP协议就只能应用于网页的浏览,只要通信的双方都遵守HTTP协议,其就有用武之地。比如腾讯QQ、迅雷等软件都是用HTTP协议(当然还包括其他的协议)。

那么HTTP协议是如何工作的呢?

首先,客户端发送一个请求给服务器,服务器在接收到这个请求后将生成响应返回给客户端。一次HTTP操作成为一个事务,其工作过程可分为四步:

1、客户机与服务器需要建立连接,单击某个超链接,HTTP协议的工作开始。

2、建立连接后,客户机发送一个请求给服务器。格式为:前边是统一资源标识符(URL)、中间是协议版本号,后边是MIME信息(包括请求修饰符、客户机信息和可能的内容)

3、服务器接收到请求后,给予相应的相应信息。格式为:首先是一个状态行(包括信息的协议版本号、一个成功或错误的代码),然后是MIME信息(包括服务器信息、实体信息和可能的内容)

4、客户端接收服务器返回的信息并显示在用户的显示屏上,然后客户机与服务器断开连接。

 

如果以上过程中的某一步出现错误,产生错误的信息将返回到客户端,有显示屏输出。对于用户来说,这些过程是由HTTP协议自己完成的,用户只要用鼠标点击等待信息显示就可以了。

请求

HTTP请求由三部分组成:请求行、消息报头、请求正文。请求行以一个请求方式符号开头,以空格分开,后面跟着请求的URI和协议的版本,格式为:Method Request-URI HTTP-Version CRLF

 

比如我请求本站某页面:http://www.veitor.net/cate/bianchengyuyan

请求行如图

QQ截图20150106220345

参数说明:

  • Method:请求方法。截图中为GET。
  • Request-URI:一个统一资源标识符,截图中为/cate/bianchengyuyan
  • HTTP-Version:请求的HTTP协议版本,截图中为1.1
  • CRLF:回车和换行符
响应

在接收和解释请求消息后,服务器返回一个HTTP响应消息。HTTP相应也由三个部分组成。分别是:状态行、消息报头、响应正文。状态行格式为:HTTP-Version Status-Code Reason-Phrase CRLF

比如我请求本站某页面:http://www.veitor.net/cate/bianchengyuyan

QQ截图20150106221255

 

参数说明如下:

  • HTTP-Version:服务器HTTP协议的版本,截图中为1.1
  • Status-Code:服务器发回的响应状态码,截图中为200
  • Reason-Phrase:状态代码的文本描述,截图中为OK

其中状态代码由三位数字组成,第一个数字定义了响应的类别,有五种可能取值:

  1. 1xx:指示信息——请求已接收,继续处理。
  2. 2xx:成功——请求已被成功接收、理解、接受
  3. 3xx:重定向——要完成请求必须进行更进一步的操作
  4. 4xx:客户端错误——请求有语法错误或请求无法实现
  5. 5xx:服务器错误——服务器未能实现合法的请求

常见的状态码、状态描述和说明如下:

  • 200 OK:客户端请求成功
  • 400 Bad Request:客户端请求有语法错误,不能被服务器所理解
  • 401 Unauthorize:请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
  • 403 Forbidden:服务器收到请求,但是拒绝提供服务
  • 404 Not Found:请求资源不存在,例如输入了错误的URL
  • 500 Internal Server Error:服务器发生不可预期的错误
  • 503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常

 

报头

HTTP消息报头包括普通报头、请求报头、响应报头、实体报头。每个报头域组成形式如下:

  1. 普通报头中有少数报头域用于所有的请求和响应消息,但并不用于被传输的实体,只用于传输的消息(如缓存控制、连接控制)
  2. 请求报头允许客户端向服务器端传递请求的附加信息以及客户端自身的信息(如UA头、Accept等)
  3. 响应报头允许服务器传递不能放在状态行中的附加响应信息,以及关于服务器的信息和对Request-URL所标的资源进行下一步访问的信息(如Location)
  4. 实体报头定义了关于实体正文和请求所标识的资源的元信息,例如有无实体正文。

 

比较重要的几个报头如下(可以结合着上面的截图看):

  • Host:头域指定请求资源的Internet主机和端口号,必须表示请求URL的原始服务器或网关的位置。HTTP1.1请求必须包含主机头域,否则系统会以400状态码返回。
  • User-Agent:简称UA,内容包含发出请求的用户信息。通常UA包含浏览者的信息,主要是浏览器的名称版本和所用的操作系统。在上面的截图中可以看到,我的客户端电脑使用的是Gecko渲染引擎的浏览器,我用的Chrome,操作系统为Windows NT6.1的内核,即Windows 7操作系统(内核版本号和操作系统代号不是一一对应的)。这个UA头不仅仅是使用浏览器才存在,只要使用了基于HTTP协议的客户端软件都会发送这个请求,无论是手机端还是PDA等,这个UA头是辨别客户端所用设备的重要依据
  • Accept:告诉服务器可以接受的文件格式,通常这个值在各种浏览器中都差不多。不过Wap浏览器所能接受的格式要少一点,这也是用来区分WAP和计算机浏览器的主要依据之一。随着WAP浏览器的升级,其已经和计算机浏览器越来越接近,因此这个判断所起的作用越来越弱。
  • Cookie:Cookie分两种,一种是客户端向服务器端发送的,使用Cookie报头,用来标记一些信息;另一种是服务器发送给浏览器的,报头为Set-Cookie。两者的区别是Cookie报头的value里可以有多个cookie值,并且不需要显式指定domain等。而set-Cookie报头里一条记录只能有一个Cookie的value,需要指明domain、path等
  • Cache-Control:指定请求和响应遵循的缓存机制。在请求消息或响应消息中设置Cache-Control并不会选择另一个消息处理过程中的缓存处理过程。请求时的缓存指令包括no-cache、no-store、max-age、max-stale、min-fresh、only-if-cached;响应消息中的指令包括public、private、no-cache、no-store、no-transform、must-revalidate、proxy-revalidate、max-age
  • Referer:头域允许客户端指定请求URI的资源地址,这可以允许服务器生成回退链表,可以用来登录、优化缓存等。也允许废除的或错误的连接由于维护的目的而被追踪。如果请求的URI没有自己的URI地址,Referer不能被发送。如果指定的是部分URI地址,则此地址应该是一个相对地址。Referer通常是流量统计系统来记录来访者地址的参数。
  • Content-Length:内容长度
  • Content-Range:响应的资源范围。可以在每次请求中标记请求的资源范围,在连接断开重连时,客户端只请求该资源未下载的部分,而不是重新请求整个资源,实现断点续传迅雷就是基于这个原理,使用多线程分段读取网络上的资源,最后再合并。
  • Accept-Encoding:指定所能接受的编码方式。通常服务器会对页面进行GZIP压缩后再输出以减少流量,一般浏览器均支持对这种压缩后的数据进行处理。但对于我们来说,如果不想接受到这些看似“乱码”的数据,可以指定不接受任何服务器端压缩处理,要求其原样返回
  • 自定义报头:在HTTP消息中,也可以使用一些HTTP1.1正式规范里没有定义的头字段,这些头字段统称为自定义的HTTP头或者扩展头。比如上面截图中X-Powered-By字段,这是由服务器发送的。在PHP里,使用header函数即可实现。

 

参考:《PHP核心技术与最佳实践》

标签: HTTP, 网络

Python乱码问题解决方法

最近要写个python脚本,遇到各种乱码问题,后来一查原来python乱码问题还真是层出不穷,让人头疼啊。

我主要是用的sublime进行脚本编写,使用cmd或python自带的GUI运行脚本调试查看。

比如下列的脚本代码在运行时会产生乱码

#coding:utf-8
print "的是"

QQ截图20150106092343因为编写代码时保存的编码为UTF-8,而在Windows中运行读取脚本时以系统的编码GBK去执行,因此GBK与UTF-8冲突导致编码错误。

解决方法有三种:

1、我们往往在代码顶部加入#coding:utf-8 这样指明解码时的字符集,而指明utf-8还是会出现所说的乱码,还是因为运行时采用的GBK与我们指定的UTF-8冲突。所以要指明编码的话就只好指明为和系统编码一样的字符集,如这样指明#coding:gbk ,不写coding的话默认为与系统一样的编码

QQ截图20150106094006

2、在打印的字符前加上u,如print u"是" ,这样就指明了使用头部coding指定的字符集解码,当然前提是要在头部添加coding,并且coding所指的的字符集与操作系统用的字符集一样(刚才第一条说明了)

QQ截图20150106094135

3、输出时指定使用的字符集,如print '是'.decode('utf-8')

QQ截图20150106094339

当然保存脚本时的编码要与coding一样。

另外据说还有种方法是加入这三行代码:

import sys
reload(sys)
sys.setdefaultencoding('utf-8')

这种方法我没试过,看起来好像是从系统编码进行下手的,将系统编码临时改掉,这样的话你们懂的。

标签: 编码, python

关于python提示no module named win32api问题的解决

本篇只介绍对于本机环境下问题的解决方案。我的系统是Win7 64位,并且安装的是64位的python2.7.6版本

当提示“no module named win32api”该信息时,需要安装pywin32模块,原本我是想通过pip安装的,但有问题。

于是上网找了对应的安装包,对应于我的python版本的,如果你也是用的2.7.6 64位的,可以下载我提供的这个附件:pywin32-218.win-amd64-py2.7 然后进行安装,如果打开程序安装报错的话,很有可能是找不到python的注册表信息,我的解决方法是重新安装python(当然也有写个脚本进行注册的方法),重装时选择只对本机用户,而不是所有用户。完成后就能成功安装pywin32了。

标签: python

sublime下配置编译和运行C程序

我使用的是sublime3,首先下载MinGW,别问我是啥,我也不怎么了解,网上说 MinGW 提供了一套简单方便的Windows下的基于GCC 程序开发环境,提供了GNU工具集。在这里我就理解它提供了gcc、g++、make等编译器吧。具体自行了解。

你可以上minGW官网http://www.mingw.org/或其他地方可以下载到,可视化安装界面,自行选择目录安装。比如我安装到了D:\minGW

安装完毕后,将minGW安装目录下的bin目录添加到环境变量,如下图,我将D:\minGW\bin添加到环境变量

QQ图片20141219150247

接着配置sublime,工具栏选择工具(tools)-编译系统,然后新建一个编译系统

QQ图片20141219150736输入以下配置:

{
	"shell_cmd": "gcc \"${file}\" -o \"${file_path}/${file_base_name}\" -std=c11 -O2 -Wall -lm --static",
	"file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$",
	"working_dir": "${file_path}",
	"selector": "source.c, source.c++",

	"variants":
	[
		{
			"name": "Run",
			"shell_cmd": "gcc -std=c11 -O2 -Wall -lm --static \"${file}\" -o \"${file_path}/${file_base_name}\" && \"${file_path}/${file_base_name}\""
		}
	]
}

保存在Sublime Text的Packages目录下即可。

然后敲一段C程序然后保存为.c文件,按ctrl+B会在这文件旁生成exe程序,按ctrl+shift+B会在sublime控制台中显示运行结果(如下图)

QQ图片20141219152044

 

至此大功告成。

标签: C, sublime

关于使用PDO无法执行两次查询的问题

今天在使用PDO查询数据时遇到这么一个问题,使用实例化的PDO类无法执行两次查询,即第一次查询是正常的,第二次查询是无效的。

举个栗子:

$pdo = new PDO('mysql:host=127.0.0.1;dbname=test;', 'root', '');

$stmt1 = $pdo->query('select count(*) from table');

$stmt2 = $pdo->query('select * from table limit 0,5');

实例化PDO类,调用类中方法query查询一条语句“select count(*) from table”,返回的$stmt1变量是一个PDOStatement对象,而此时没有使用$stmt1这个实例做任何操作(或者只是使用了$stmt1->fetchColumn()获得了数据数量)。接着再调用PDO实例中的方法query(使用prepare后再execute也一样)再一次查询数据,这是返回给$stmt2变量的值就不是一个PDOStatement对象了,而是false。使用$stmt2->errorInfo();打印出结果可以看到这么个错误“Cannot execute queries while other unbuffered queries are active.  Consider using PDOStatement::fetchAll().  Alternatively, if your code is only ever going to run against mysql, you may enable query buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute”

大致意思是说,当一个未缓存查询正在活动时不能再执行查询操作。解决方式有:

1、可以考虑使用前一个查询返回的PDOStatement对象(本例子中是$stmt1)中fetchAll方法把数据处理完毕

2、亦或者是将前一个查询PDOStatement对象设为null:$stmt1=null

3、再或者是连接数据库后使用PDO实例中方法$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true); 设置mysql启用缓冲查询。

这时第二次查询时$stmt2就不会为false了。

标签: mysql, PDO

Mysql转换表的引擎的三种方法

ALTER TABLE

将表从一个引擎修改为另一个引擎最简单的办法就是使用ALTER TABLE语句。下面将mytable的引擎修改为InnoDB:

mysql> ALTER TABLE `mytable` ENGINE = InnoDB;

上述语法可以适用任何存储引擎。担忧一个问题:需要执行很长时间。Mysql会按行将数据从原表复制到一张新的表中,在赋值期间可能会小号系统所有的I\O能力,同时原表上会加上读锁。所以,在繁忙的表上执行此操作要特别小心。一个替代方案是采用下面的导出与导入的方法,手工进行表的复制。

导出与导入

为了更好的控制转换过程,可以使用mysqldump工具将数据导出到文件,然后修改文件中CREATE TABLE语句的存储引擎选项,注意同时修改表名,因为同一个数据库中不能存在相同的表名,即使它们使用的是不同的存储引擎。同时要注意mysqldump默认会子的那个在CREATE TABLE语句前加上DROP TABLE语句,不注意这一点可能会导致数据丢失。

创建与查询(CREATE和SELECT)

第三种转换的技术综合了第一种方法的高效和第二种方法的安全。不需要导出整个表的数据,而是先创建一个新的存储引擎的表,然后利用INSERT...SELECT语法来导数据:

mysql> CREATE TABLE `innodb_table` LIKE `myisam_table`;
mysql> ALTER TABLE `innodb_table` ENGINE = InnoDB;
mysql> INSERT INTO `innodb_table` SELECT * FROM `myisam_table`;

数据量不大的话,这样做工作的很好。如果数量很大,则可以考虑做分批处理,针对每一段数据执行事务提交操作,以避免大事务产生过多的undo。假设有主键字段id,重复运行以下语句(最小值x和最大值y进行相应的替换)将数据导入到新表:

mysql> START TRANSACTION;
mysql> INSERT INTO `innodb_table` SELECT * FROM `myisam_tale` BETWEEN x AND y;
mysql> COMMIT;

这样操作完成以后,新表示原表的一个全量复制,原表还在,如果需要可以删除原表,如果有表要,可以在执行的过程中对原表加锁,以确保新表和原表的数据一致。

 

Percona Tolkit提供了一个pt-online-schema-change的工具(基于Facebook的在线schema变更技术),可以比较简单、方便的执行上述过程,避免手工操作可能导致的失误和繁琐。

标签: none

了解mysql中多版本并发控制

前面写的一篇《了解mysql的并发控制》中了解了mysql的并发控制,主要是通过锁机制来控制并发的。控制严格的锁会影响性能,反之则影响数据安全性。而mysql的大多数事务性存储引擎实现的都不是简单的行级锁。基于提升并发性能的考虑,它们一般都同时实现了多版本并发控制(MVCC)。

可以认为MVCC是行级锁的一个变种,但是它在很多情况下避免了加锁操作,因此开销更低。虽然实现机制有所不同,但大都实现了非阻塞的读操作,写操作也只锁定必要的行。

MVCC的实现,是通过保存数据在某个时间点的快照来实现的。也就是说,不管需要执行多长时间,每个事务看到的数据都是一致的。下面就通过InnoDB的简化版行为说明MVCC是如何工作的:

InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存行的过期时间(也叫删除时间)。当然存储的并不是实际的时间值,而是系统的版本号。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号作为事务的版本号,用来和查询到的每行记录的版本号进行比较。下面看一下在REPEATABLE READ(可重复读)隔离级别下,MVCC具体是如何操作的。

SELECT

InnoDB会根据以下两个条件检查每行记录:

a.InnoDB只查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务的读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。

b.行的删除版本要么未定义,要么大于当前事务版本号。这可以确保事务读取到的行,在事务开始之前未被删除。

INSERT

InnoDB为新插入的每一行保存当前系统版本号作为行版本号。

DELETE

InnoDB为删除的每一行保存保存当前系统版本号作为行删除标识。

UPDATE

InnoDB为插入一行新记录,保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为删除标识。

 

保存这两个额外系统版本号,使大多数读操作都都可以不用加锁。这样设计使得读数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行。不足之处是每行记录都会需要额外的存储空间,需要做更多的行检查工作,以及一些额外的维护工作。

MVCC只在REPEATABLE READ和READ COMMITTED两个隔离级别下工作。其他两个隔离级别都和MVCC不兼容。因为READ UNCOMMITTED总是读取到最新的数据行(脏读),而不是符合当前事务版本的数据行。而SERIALIZABLE则会对所有读取的行都加锁。(这段内容需要了解mysql隔离级别,请看这篇文章

如果有疑问,欢迎留言探讨。

标签: mysql, 事务

了解mysql事务

mysql事务可能大多数人都有所了解,本篇博文主要记录我了解的事务的一些细节之处,也必然对事务能有进一步的理解。

看过《高性能mysql》一书,上面写道事务就是一组原子性的sql查询,或者说一个独立的工作单元。可能这句话并不是那么好理解,什么叫原子性?什么又是独立的工作单元?看几个例子和几个名词解释或许更加有帮助。

在事务中,如果数据库引擎能够成功地对数据库应用一组查询的全部语句,那么就执行这组查询。如果其中有任何一条语句因为崩溃或其他原因无法执行,那么所有的语句都不会执行。也就是说,事务内的语句,要么全部执行成功,要么全部执行失败。

讲解事务最经典的例子还是银行转帐。假设银行的数据库有两张表:支票表和储蓄表,现在要从用户A的支票账户转义200元到他的储蓄帐户,那么至少需要三个步骤

1、检查支票账户的余额高于200元。

2、从支票账户余额中减去200元。

3、在储蓄帐户余额中增加200元。

上面三个步骤操作必须打包在一个事务中,其中任何一个步骤失败,则必须回滚所有步骤,即之前操作的几个步骤都失效。

一般可以用START TRANSACTION语句开始一个事务,然后要么使用COMMIT提交事务将修改的数据持久保留,要么使用ROLLBACK撤销所有的修改。上面三个步骤用语句可以这样表示:

1、START TRANSACTION;

2、SELECT balance FROM checking WHERE customer_id=123123;

3、UPDATE checking SET balance = balance -200 WHERE customer_id=123123;

4、UPDATE savings SET balance = =balance + 200 WHERE customer_id=123123;

5、COMMIT;

单纯的讲事务还是不够的。试想一下,如果执行到第四条语句时服务器崩溃了,会发生什么?用户可能会损失200元。再假如,在执行到第三条语句和第四条语句之间时,另一个进程要删除支票账户的所有余额,那么结果可能就是银行在不知情的状况下白了用户200元。

由此看来空谈事务还是不够的,需要通过严格的ACID测试,一个运作良好的事务系统必须具备四个特性,即ACID。这是本篇博文重点要记的,也是我所说的需要记忆的细节之处。下面就介绍一下ACID:

A、原子性(atomicity)

一个事务必须被视为一个不可分割的最小工作单位,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作。这也就解开了本篇博文开头的疑问。

C、一致性(consistency)

数据库总是从一个一致性的状态转换到另一个一致性的状态。在前面的例子中,即使在执行第三、四条语句之间时系统崩溃,支票账户中也不会损失200元,储蓄帐户也不会多出200元。因为事务最终没有提交,如果所有语句都顺利执行完成,并且事务也提交成功,那么就转换到了另一个一致性。尤其是在进行多表update的时候,根据业务的操作,在更新前是一个一致性状态,更新成功后又是另一个一致性状态(因为更新后的数据结果就是预期的结果)。

I、隔离性(isolation)

通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的,在前面的例子中,当执行完第三条语句,第四条语句还未开始时,此时有另外一个帐户汇总程序开始运行,则其看到的支票账户的额并没有减去200元。在下一篇博文中将理解讨论隔离级别(isolation level),那时你会发现为什么要说“同样来说”是不可见的。当然,关于隔离级别见这篇文章《Mysql事务以及隔离级别》。

D、持久性(durability)

一旦事务提交,则其所做的修改就会永久保存到数据库中。此时系统崩溃也不会影响到已经修改的数据了。话说持久性是个模糊的概念,因为实际上持久性也分很多不同的级别。有些持久性策略能够提供非常强的安全保障,而有些未必。而且不可能有能做到100%的持久性保证的策略(如果数据库本身就能做到真正的持久性,那么还需要备份来增加持久性做什么呢?).

 

虽然事务的ACID特性可以增加事务处理过程中额外的安全性,但如同锁粒度的升级会增加系统开销一样(详见上一篇博文《了解mysql的并发控制》),这也会需要数据库系统做更多的工作。因此一个实现了ACID的数据库,相比没有实现ACID的数据库,通常会需要更强的CPU处理能力、更大的内存和更多的磁盘空间。当然用户可以根据业务是否需要事务处理,来选择合适的存储引擎。对于一些非事务性的存储引擎,可以获得更高的性能。

本篇重点理解ACID的概念,如有疑问,欢迎留言探讨。

标签: mysql, 事务, ACID

了解mysql的并发控制

关于mysql的并发控制,主要涉及“锁”这个概念。本篇博文为纯文字理论,看似可能有点枯燥,但易于理解也易懂。

何谓“锁”,“锁”是用来干嘛的?我们先来看一个例子,我们以Unix系统的email box为例,这是一个邮箱,邮箱中的所有邮件都串行在一起,彼此首尾相连。这种格式对于读取和分析邮件信息非常友好,同时投递邮件也很容易,只要在文件末尾附加新的邮件内容即可。

那么,问题来了!

如果有两个进程同一时刻对同一个邮箱投递邮件,会发生什么情况?显然,邮箱的数据会被破坏,两份邮件的内容会交叉的附加在邮箱文件的末尾。那么我们就需要一种东西来控制邮件的投递,使得一个客户投递邮件时,将邮箱锁住,此时其他客户无法对此邮箱进行投递而进入等待,直到这个客户投递完毕解开锁才能进行投递。这就是所谓的“锁”,与mysql中的“锁”概念类似。

在mysql中主要有两种锁,一个是共享锁(shared lock),一个排他锁(exclusive lock),也分别称为读锁(read lock)和写锁(write lock)。

读锁是共享的,或者说是相互不阻塞的。多个客户在同一时刻可以同时读取同一个资源,而互不干扰。写锁则是排他的,也就是说一个写锁会阻塞其他的写锁和读锁,这是处于安全策略的考虑,只有这样,才能确保在给定的时间内只有一个用户能执行写入,并防止其他用户读取正在写入的同一资源。

锁粒度

mysql该如何加锁?修改数据时将全部数据都锁定?还是只是锁定要修改的那一块数据?加锁会不会影响性能?这就是要讨论的锁的粒度大小。

锁的粒度影响着共享资源的并发性,所以加锁时应该选择明确的锁定对象。尽量只锁定需要修改的部分数据,而不是所有数据。更理想的方式是,只对会修改的数据片进行精确的锁定。任何时候,在给定的资源上,锁定的数据量越少,则系统的并发程度越高。

however,加锁也是需要消耗资源的,锁的各种操作,包括获得锁、检查锁是否已经解除、释放锁等,都会增加系统开销。

因此,我们需要在追求并发性和数据安全性之间寻求平衡,即如何加锁,制定一个锁策略。

mysql中提供了多种的加锁选择,每种mysql存储引擎都可以实现自己的锁策略和锁粒度。在存储引擎的设计中,锁管理是个非常重要的决定。将锁粒度固定在某个级别,可以为某些特定的应用场景提供更好的性能,但同时却会失去对另外一些场景的良好支持。好在mysql支持多个存储引擎的架构,所以不需要单一的通用的解决方案。

下面介绍两种锁策略:

表锁(table lock)

表锁是mysql中最基本的锁策略,并且是开销最小的策略。表锁非常类似前文描述的邮箱加锁机制:它会锁定整张表。一个用户在对表进行写操作(插入、修改、删除)钱,需要先获得写锁,这会阻塞其他用户对该表的所有读写操作。只有没有写锁时,其他读取的用户才能获得读锁,读锁之间是不会相互阻塞的。

在特定的场景中,表锁也可能有良好的性能。写锁比读锁有更高的优先级,因此一个写锁请求可能会被插入到读锁队列的前面,而读锁则不能插入到写锁的前面。

尽管存储引擎可以管理自己的锁,mysql本身还是会使用各种有效的表锁来实现不同的目的。例如,服务器(详见前面的逻辑架构一文)会为诸如ALTER TABLE之类的语句使用表锁,而忽略存储引擎的锁机制。

行级锁(row-level lock)

行级锁可以最大程度的支持并发处理,同时也带来了最大的锁开销。在InnoDB以及其他一些存储引擎中实现了行级锁。行级锁只在存储引擎层实现,而mysql服务器层没有实现。

 

以上这些就是对mysql并发控制的理解,通过锁机制来有效控制并发。

如有疑问欢迎留言探讨,毕竟我也是刚开始深入学习mysql。

标签: mysql, 并发, 存储引擎