mysql - 表锁,行锁
MyISAM存储引擎:开销小,加锁快,无死锁。锁定粒度大,并发度低,容易发生锁冲突。 不支持事务。 采用表锁 (操作时对操作的表上锁) 基本不用了
innoDB存储引擎:开销大,有死锁,锁定粒度小,不容易发生冲突。 采用行锁(每句sql执行时对操作行上锁),但是也支持表锁 mysql默认引擎
问题:库存超卖
库存1,结果两个人同时购买,第一个人判断后还有1个剩余,于是执行 update 操作, 这时第二个人开始判断,此时update还没结束,剩余还是1,于是也开始进update操作,导致库存变为-1.
1. 悲观锁,在判断的select语句中加入行锁,与update语句互斥,保证第1个人提交事务前,第2个人不能操作这个货物。
2.乐观锁,添加版本字段,查询时更新版本字段,版本字段变化时,不能更新。
select num, version from warehouse where id = 商品编号; 查询数量同时查出版本,比如111
update warehouse set num = num - 1, version = version + 1 where version = 111; 这样假如warhouse在查询后这个商品被更改过,这条将不会更改任何字段。如果成功了版本同时改变为112。
reference: http://www.zsythink.net/archives/1233/
事务隔离:
读未提交: read-uncommitted 可能问题:脏读,幻读,不可重复读 A修改操作后没有提交,B也能看到改动
读已提交:read-commited 可能问题:幻读,不可重复读 A修改操作后提交了,B可以看到改动
可重复读: repeatable-read 可能问题:不可重复读 A修改操作后,B的操作也用到了相同的表后可以看到A的改动
可序列化:serialiazable 三种都不会出现 A修改操作时,B连读取都做不到。 这样最安全,但是无法并发,效率太低。
脏读: 读到了别人未提交的改动 (表的数据实际没有变化,但是你以为变了) B修改了数据还没提交,A查到了,B的数据提交失败回滚了,A以为B改完了。
幻读: 数量不对 ( 表的数据数量变化) A在查询后, B添加或者删除了数据, A以为数据没变。
不可重复读: 读取和修改时状态不一致 (数据值发生变化) A在查询后,B修改了数据值,A还是以旧数据作为判断条件。
不可重复读 和 幻读差不多,不过一个是莫名其妙多了一行,一个是莫名其妙值变了。 所以库存超卖是不可重复读。
1.
查询数据库事务隔离等级:
SHOW VARIABLES LIKE ‘%isolation‘; #网上查的版本是tx_isolation, 我的版本是 transaction_isolation,所以直接用模糊查询
一般都是 可重复读级别
2.修改隔离等级:
SET transaction_isolation = ‘read-uncommitted‘;
mysql的锁: 表名 index_test
1. 查看表存储引擎
SHOW TABLE STATUS LIKE ‘index_test‘;
2. 修改表存储引擎
ALTER TABLE index_test ENGINE = MYISAM; #修改表引擎 SHOW TABLE STATUS LIKE ‘index_test‘;
3. 表锁
表锁有读锁,和写锁
读锁:不让其他连接修改表, 是可以查询的!
写锁:不让其他连接查询和修改表。
- -|||注意别只凭字面意思理解,有时候容易造成读锁是不让读,写锁是不让写的错觉(比如我。。。)
1).对index_test 添加表写锁 (这时在当前数据库连接会话下是可以修改表的。)
LOCK TABLE index_test WRITE;
2). 新建一个数据库连接(文件 -> 新链接 不是新打开一个查询编辑器或者窗口= =sqlyog打开新窗口还是用的同一个链接。。。)
然后修改index_test表 (查询也是一样的结果)
可以看到 会一直显示处理中。
3).这时在第一个连接中解锁表
UNLOCK TABLES;
发现第二个连接的修改语句立刻执行完毕。
个人测试(我有一个大胆的想法):
建立连接a,b,c
a锁表
b修改 - 进入等待
c输入解锁
b依旧等待
c锁表 - 进入等待
a解锁 - b执行完毕,c执行完毕
a锁表 - 进入等待。。。因为c把表锁了
c解锁
可以得出结论:不同的连接的锁表和解锁是独立的。。。
4).对index_test 添加表读锁 (这时在当前数据库连接会话下是可以修改表的。)
LOCK TABLE index_test READ;
这时第二个连接,修改时会进入等待状态,但是可以查询。
4. 行锁
myisam 只有表级锁,所以要切换回innodb
行锁:执行语句时,只锁住相关数据行,而不是整个表。 这也是innodb能支持事务的主要原因之一。(要是锁表,那么一次事务中用到多个表的时候,会导致数据库很多表被锁,大幅拖慢效率)
innodb会自动给修改语句添加行锁。
1. 修改表引擎
ALTER TABLE index_test ENGINE = INNODB;
2.取消自动提交(innodb会把单独的sql语句作为事务直接提交,取消后必须commit才能提交一次事务)
SET autocommit = 0;
3. 进行一次修改
UPDATE index_test SET key1 = 1 WHERE t_id = 1;
新建连接2,对表进行修改
UPDATE index_test SET key1 = 3 WHERE t_id = 1;
连接2 进入等待
过一段时间后提示超时
连接1 提交修改
COMMIT;
连接2 再次尝试修改,成功
个人测试:既然是行锁,那么尝试在连接2修改不同行的数据看看结果-。-
连接1:
UPDATE index_test SET key1 = 1 WHERE t_id = 1;
连接2:
UPDATE index_test SET key1 = 3 WHERE t_id = 2;
连接2直接修改成功。
5.为查询语句添加行锁
悲观锁解决库存超卖问题:
悲观锁,在判断的select语句中加入行锁,保证第1个人提交事务前,第2个人不能操作这个货物。
用index_test 表作为例子
t_id = 1这一行中, key = 1。假设为key1 是一种货物,剩余1个
a 进行查询,同时上行锁,准备更新
SELECT * FROM index_test WHERE t_id = 1 FOR UPDATE;
b此时开始查询
SELECT * FROM index_test WHERE t_id = 1 FOR UPDATE;
产生读读互斥,b进入等待,等待a提交。
a更新并提交
UPDATE index_test SET key1 = key1 - 1 WHERE t_id = 1; COMMIT;
a提交后,b查出结果
key1已经变成了0
从而避免了库存超卖。
死锁:
和java的差不多。。。
a:查询A,上锁A, 等查到后修改B, 然后提交
b:查询B,上锁B, 等查到后修改A, 然后提交
对于a,需要修改B才能提交事务,然后解锁A。
对于b,需要修改A才能提交事务,然后解锁B。
形成死锁。
彼此占用钥匙。
解决:kill掉其中一个线程。(图形界面比如sqlyog中直接点个x就完事了。。。不过linux开发有时要用名命令行的那种 - -)