当我们购买一件商品时,一般会有以下几个操作:
- 查询商品信息
- 将商品信息和用户信息插入订单表
- 更新商品信息,比如状态更新为已被购买
当单条线程执行这些操作的时候肯定不会出现问题,但是当高并发的时候,会出现商品会重复购买的情况.这时候我们就要加锁了.
goods
CREATE TABLE `goods` (
`id` int(11) NOT NULL COMMENT '主键id',
`name` varchar(32) DEFAULT NULL COMMENT '商品名称',
`status` tinyint(3) DEFAULT NULL COMMENT '商品状态 0:未被购买 1: 已被购买',
`version` int(11) DEFAULT NULL COMMENT '版本号,每次更新版本号 + 1',
`create_time` bigint(20) DEFAULT NULL COMMENT '创建时间(精确到毫秒)',
`update_time` bigint(20) DEFAULT NULL COMMENT '更新时间(时间戳,精确到毫秒)',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品表';
orders
CREATE TABLE `orders` (
`id` int(11) NOT NULL COMMENT '注解id',
`goods_id` int(11) DEFAULT NULL COMMENT '商品id',
`user_id` int(11) DEFAULT NULL COMMENT '用户id',
`status` tinyint(4) DEFAULT NULL COMMENT '订单状态 0:刚创建 1:下单完成 -1:下单失败',
`create_time` bigint(20) DEFAULT NULL COMMENT '创建时间',
`update_time` bigint(20) DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='订单表';
加锁可分为乐观锁和悲观锁
乐观锁
乐观锁,顾名思义,它的想法比较乐观,认为每次查询都不会涉及到更新,所以只有到更新的时候再判断查询到的记录是否发生变化,如果没有发生变化就执行更新操作,如果已经发生了变化就不执行更新操作.
怎么判断数据是否发生变化
- 给表添加一个updateTime字段,每次更新的时候先判断updateTime是否与查询到updateTime一致,如果一致就更新数据,并且更新updateTime,否则不更新
//查询id = #id 商品 goods
SELECT id, name, status, create_time, update_time, version FROM GOODS WHERE id = '#id';
//插入订单表
INSERT INTO ORDERS VALUES(id, goods_id, user_id,...);
//更新goods状态,这里的update_time是上面查到的goods的update_time
UPDATE GOODS SET status = 1, update_time = now() WHERE id = '#id' AND update_time = '#update_time';
- 给表添加一个version字段,每次更新的时候仙判断version是否和查询到的version一致,如果一致就更新数据,并且更新version = version + 1,否则不更新(操作和update_time更新相似,只是update_time改成version)
悲观锁
悲观锁,顾名思义,就是想法比较悲观,它认为每次查询都可能涉及到更新,所以在查询的时候就给记录加上锁,其它线程就不会执行更新操作了,只有等释放锁了才能执行更新操作.
怎么给记录加锁
- 共享锁(读锁,S锁)
SET AUTOCOMMIT = 0;
BEGIN;
SELECT id, name, status, create_time, update_time, version FROM GOODS WHERE id = '#id' LOCK IN SHARE MODE;
COMMIT;
在mysql事务中查询语句后面加上 lock in share mode,即可得到记录的共享锁;什么叫做共享呢?就是当共享锁还没释放的时候,其他查询用lock in share mode也可以得到记录的共享锁.那共享锁有什么用呢?共享锁被占用的记录不能被更新或者不能被select ... for update 得到排它锁,否则会被阻塞.
- 排它锁
SET AUTOCOMMIT = 0;
BEGIN;
SELECT id, name, status, create_time, update_time, version FROM GOODS WHERE id = '#id' FOR UPDATE;
COMMIT;
在mysql事务中查询语句后面加上 for update 或者 更新记录的时候,即可得到记录的排它锁;什么叫做排他锁呢?排它锁被占用的记录,不允许其他线程获得他的排它锁,甚至共享锁,就是说记录不能被更新或者 其他线程的for update 到,否则会阻塞.直到排它锁释放了才能继续执行.
注意,不管是共享锁还是排它锁,当我们用不加锁的select 查询记录的时候也是可以查询到的.
网友评论