月度归档:2015年05月

MySQL 使用SELECT FOR UPDATE 做事务写入前的确认

MySQL  使用SELECT … FOR UPDATE 做事务写入前的确认

以MySQL 的InnoDB 为例,预设的Tansaction isolation level 为REPEATABLE READ,在SELECT 的读取锁定主要分为两种方式:

SELECT … LOCK IN SHARE MODE SELECT … FOR UPDATE

这两种方式在事务(Transaction) 进行当中SELECT 到同一个数据表时,都必须等待其它事务数据被提交(Commit)后才会执行。而主要的不同在于LOCK IN SHARE MODE 在有一方事务要Update 同一个表单时很容易造成死锁 。

简单的说,如果SELECT 后面若要UPDATE 同一个表单,最好使用SELECT … UPDATE。

举个例子: 假设商品表单products 内有一个存放商品数量的quantity ,在订单成立之前必须先确定quantity 商品数量是否足够(quantity>0) ,然后才把数量更新为1。

不安全的做法:

SELECT quantity FROM products WHERE id=3; UPDATE products SET quantity = 1 WHERE id=3;

为什么不安全呢?

少量的状况下或许不会有问题,但是大量的数据存取「铁定」会出问题。

如果我们需要在quantity>0 的情况下才能扣库存,假设程序在第一行SELECT 读到的quantity 是2 ,看起来数字没有错,但是当MySQL 正准备要UPDATE 的时候,可能已经有人把库存扣成0 了,但是程序却浑然不知,将错就错的UPDATE 下去了。

因此必须透过的事务机制来确保读取及提交的数据都是正确的。

于是我们在MySQL 就可以这样测试: (注1)

SET AUTOCOMMIT=0; BEGIN WORK; SELECT quantity FROM products WHERE id=3 FOR UPDATE;

===========================================

此时products 数据中id=3 的数据被锁住(注3),其它事务必须等待此次事务 提交后才能执行

SELECT * FROM products WHERE id=3 FOR UPDATE (注2) 如此可以确保quantity 在别的事务读到的数字是正确的。

===========================================

UPDATE products SET quantity = ‘1’ WHERE id=3 ; COMMIT WORK;

===========================================

提交(Commit)写入数据库,products 解锁。

注1: BEGIN/COMMIT 为事务的起始及结束点,可使用二个以上的MySQL Command 视窗来交互观察锁定的状况。

注2: 在事务进行当中,只有SELECT … FOR UPDATE 或LOCK IN SHARE MODE 同一笔数据时会等待其它事务结束后才执行,一般SELECT … 则不受此影响。

注3: 由于InnoDB 预设为Row-level Lock,数据列的锁定可参考这篇。

注4: InnoDB 表单尽量不要使用LOCK TABLES 指令,若情非得已要使用,请先看官方对于InnoDB 使用LOCK TABLES 的说明,以免造成系统经常发生死锁。
MySQL SELECT … FOR UPDATE 的Row Lock 与Table Lock

上面介绍过SELECT … FOR UPDATE 的用法,不过锁定(Lock)的数据是判别就得要注意一下了。由于InnoDB 预设是Row-Level Lock,所以只有「明确」的指定主键,MySQL 才会执行Row lock (只锁住被选取的数据) ,否则MySQL 将会执行Table Lock (将整个数据表单给锁住)。

举个例子:

假设有个表单products ,里面有id 跟name 二个栏位,id 是主键。

例1: (明确指定主键,并且有此数据,row lock)

SELECT * FROM products WHERE id=’3′ FOR UPDATE;

例2: (明确指定主键,若查无此数据,无lock)

SELECT * FROM products WHERE id=’-1′ FOR UPDATE;

例2: (无主键,table lock)

SELECT * FROM products WHERE name=’Mouse’ FOR UPDATE;

例3: (主键不明确,table lock)

SELECT * FROM products WHERE id<>’3′ FOR UPDATE;

例4: (主键不明确,table lock)

SELECT * FROM products WHERE id LIKE ‘3’ FOR UPDATE;

注1: FOR UPDATE 仅适用于InnoDB,且必须在事务区块(BEGIN/COMMIT)中才能生效。

注2: 要测试锁定的状况,可以利用MySQL 的Command Mode ,开二个视窗来做测试。

什么时候需要使用for update?就是那些需要业务层面数据独占时,可以考虑使用for update。场景上,比如火车票订票,在屏幕上显示邮票,而真正进行出票时,需要重新确定一下这个数据没有被其他客户端修改。所以,在这个确认过程中,可以使用for update。这是统一的解决方案方案问题,需要前期有所准备

==============================

由于InnoDB预设是Row-Level Lock(行级锁),所以只有「明确」的指定主键,MySQL才会执行Row lock (只锁住被选取的资料例) ,否则MySQL将会执行Table Lock (表锁)。

1.当明确指定主键,并且有此资料时,锁的是where后面的记录,即这里的id= ? 这一条记录
2.明确指定主键,若查无此笔资料,无lock
3.无主键,table lock  ( SELECT * FROM user WHERE name =’ethan’ FOR UPDATE)
4.主键不明确,table lock (SELECT * FROM user WHERE id<>’5′ FOR UPDATE)

FOR UPDATE仅适用于InnoDB,且必须在事务区块(BEGIN/COMMIT)中才能生效。

==========================================

SELECT … LOCK IN SHARE MODE sets a shared mode lock on the rows read. A shared mode lock enables other sessions to read the rows but not to modify them. The rows read are the latest available, so if they belong to another transaction that has not yet committed, the read blocks until that transaction ends.
在读取的行上设置一个共享模式的锁。这个共享锁允许其它session读取数据但不允许修改它。 行读取的是最新的数据,如果他被其它事务使用中而没有提交,读取锁将被阻塞直到那个事务结束。

SELECT … FOR UPDATE sets an exclusive lock on the rows read. An exclusive lock prevents other sessions from accessing the rows for reading or writing.
在读取行上设置一个排他锁。阻止其他session读取或者写入行数据

如何进行wordpress的手动更新

1.备份旧版wordpress资料,包括数据库和服务器内的文件。

2.从WP中文官网下载最新版WordPress,下载完毕解压到你电脑上。

3.删除博客主机上的wp-includes和wp-admin目录。

4.将解压在本地电脑的wordpress文件夹中除了wp-content目录外的所有文件都上传并覆盖到你博客主机相对应的位置。

5.执行升级步骤,运行http://你的博客地址/wp-admin/upgrade.php,将你的博客地址填入路径中执行升级程序。

使用nginx proxy_pass 配合SSH隧道实现内网80端口映射

 

微信公众号对接服务器必须为80端口,给开发时本机实时调试造成很多麻烦。

如何进行微信公众平台开发测试?下面介绍几种常见方法

1.远程调试
将代码发布到服务器上进行测试,可以使用git或svn在提交代码后,自动部署到开发测试服务器上。效率低。

2.在本地路由器上做端口映射,直接将80端口映射到开发电脑中。如果有条件的,这种方式比较方便,但网络提供商一般屏蔽80端口,如果是ADSL,ip地址不固定,给实际操作,带来很多不确定因素。

3.使用神器ngrok,将本机80直接映射到公网,可惜服务器在国外,网络也比较慢还经常被墙。

4.使用nat123进行80网站映射,未找到mac版客户端,未做测试。

5.使用nginx proxy_pass 配合SSH隧道,完美!

nginx配置

server {
listen       80;
server_name 你的域名;

location / {
proxy_pass http://localhost:8080;

proxy_redirect off;

proxy_set_header Host $host;

proxy_set_header X-Real-IP $remote_addr;

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Cookie $http_cookie;

}
}

开发电脑中,执行一条命令即可

ssh -CNg -R 8080:127.0.0.1:80 -o TCPKeepAlive=yes -o ServerAliveInterval=10 -o ServerAliveCountMax=2 -o ExitOnForwardFailure=yes 用户名@服务器ip

 如果断开后连不上,提示端口已占用,在服务器上运行 
#lsof -i:8080  #查看端口被哪个进程占用8080端口,然后kill -9