作者sunz5010 (FoFo)
看板PHP
标题Re: [请益] 同时写入资料库的问题
时间Fri Mar 25 10:55:21 2011
: : → cokellen:楼上,MySql ,innodb, transaction
: : → sunz5010:我用一个新的table的PK、然後用insert去做
: : → sunz5010:要写入资料库之前、先inset这个PK、可以insert就写入
: : → sunz5010:不能insert就等待别人delete之後再做写入资料库
: : → sunz5010:因为用mysql insert一个pk不会有重复的问题
: : → sunz5010:如果有人先抢先写入、後来的人会出现1064的error code
: : → sunz5010:等第一个人delete之後、第二个人就能正常inset
: : → sunz5010:利用这个功能就可以达到[LOCK]的效果、还不错用@@
: 假设您有个栏位纪录数字,叫做 qty 然後有个 pkey 叫做 id 。
: 提供一个作法给您参考:
: /**
: CREATE TABLE `scart` (
: `id` int(11) NOT NULL AUTO_INCREMENT,
: `title` varchar(10) NOT NULL,
: `qty` int(11) NOT NULL,
: PRIMARY KEY (`id`)
: ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8
: */
: $id = 1;
: $qty = 2;
: $dbh = new PDO('mysql:host=localhost;dbname=benchmark','test','password',
: array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"));
: $dbh->beginTransaction();
: $sth = $dbh->prepare("SELECT qty FROM scart WHERE id = :id FOR UPDATE");
: 用 select for update 在 commit (rollback) 前都会有 exclusive lock (row level)
: $sth->execute(array(':id'=> $id));
: $result = $sth->fetch();
: if(($result['qty'] - $qty) >= 0)
: {
: $sth = $dbh->prepare("UPDATE scart SET qty = qty - :qty WHERE id = :id");
: $sth->execute(array(':qty' => $qty, ':id' => $id));
: $dbh->commit();
: }
: else
: {
: echo "not enough stock." . PHP_EOL;
: $dbh->rollback();
: }
: 以上是先拿出库存再来算数量,但是您要考虑 innodb_lock_wait_timeout ,
: 这个预设是 50 秒,等起来有点久。或是考虑另一种作法,不要 select 直接计算
: $sth = $dbh->prepare("UPDATE scart SET qty = qty - :qty
: WHERE id = :id and qty - :qty > 0");
: $sth->execute(array(':qty'=>$qty, ':id'=>$id));
: 然後 update 失败就代表库存不够,叫使用者重新回页面购买。
谢谢你的分享、我也顺便分享一下我的作法
我原本的问题是
「仓库」:1颗苹果
「甲」=>想买苹果
「乙」=>想买苹果
而买苹果的机制如为(1)先检查有没有苹果 (2)有的话就买
但要让整个机制都在Critical Secion底下、就需要一个lock
观念是
「Lock资源」=>篮子
机制是=>谁先抢到「篮子」、就能购买
if(篮子)
{
(1)检查有没有苹果
(2)有的话就买
}
else //拿不到篮子的情况
{
等待拿篮子、并且尝试拿取
}
让我困扰的是、要制作这个Lock资源、必须要有一个具有实现lock功能的工具
於是我想到了MySQL的Primary Key
我的作法是、创造一个「MyLock」的Table
里面可以有很多资讯、但主要的就是要有一个LockName这个PrimaryKey
(我这个Table其实也只放了这一个栏位)
所以作法如下:
/*****Start*****/
//买东西之前
reslut = MySQL(insert into MyLock (LockName) value ("篮子"))
while(result=>ErrorCode == 1062) //1062:重复的PK
{
//有人拿了篮子、等待一秒、之後尝试拿篮子
sleep(1); //先等待一个时间
result = MySQL(insert into MyLock (LockName) vlaue ("篮子");
}
//Critical Secion [Start]
(1)检查仓库有没有苹果
(2)有的话就买
//Critical Secion [End]
MySQL(delete from MyLock where LockName = '篮子') //释放篮子资源
/*******End*******/
这个方式挺容易的
而且没有复杂的MySQL判断跟指令
另外跟原本的lock 观念也一致
分享给大家@@、如果我的方法有什麽问题也欢迎讨论
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 175.180.127.239
1F:→ sunz5010:补充:有时候Critical Section要做的事情不只一样 03/25 10:58
2F:→ sunz5010:所以总不能都指望要做的事情只在资料库操作上 03/25 10:59
3F:→ sunz5010:用这样的方式、就能确保其他跟DB无关的操作也能够被保护 03/25 11:00
4F:→ sunz5010:Critical Section里面 03/25 11:00