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,所以直接用模糊查询

mysql - 表锁,行锁

一般都是 可重复读级别

2.修改隔离等级:

SET transaction_isolation = ‘read-uncommitted‘;

mysql的锁:   表名 index_test

1. 查看表存储引擎

SHOW TABLE STATUS LIKE ‘index_test‘;

mysql - 表锁,行锁

 2. 修改表存储引擎

ALTER TABLE index_test ENGINE = MYISAM;   #修改表引擎
SHOW TABLE STATUS LIKE ‘index_test‘;

mysql - 表锁,行锁

3. 表锁

表锁有读锁,和写锁

读锁:不让其他连接修改表, 是可以查询的!

写锁:不让其他连接查询和修改表。

- -|||注意别只凭字面意思理解,有时候容易造成读锁是不让读,写锁是不让写的错觉(比如我。。。)

1).对index_test 添加表写锁   (这时在当前数据库连接会话下是可以修改表的。)

LOCK TABLE index_test WRITE;

2). 新建一个数据库连接(文件 -> 新链接     不是新打开一个查询编辑器或者窗口= =sqlyog打开新窗口还是用的同一个链接。。。)

然后修改index_test表  (查询也是一样的结果)

mysql - 表锁,行锁

可以看到 会一直显示处理中。

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;

mysql - 表锁,行锁

3. 进行一次修改

UPDATE index_test SET key1 = 1 WHERE t_id = 1;

新建连接2,对表进行修改

UPDATE index_test SET key1 = 3 WHERE t_id = 1;

连接2 进入等待

mysql - 表锁,行锁

过一段时间后提示超时

mysql - 表锁,行锁

连接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 表作为例子

mysql - 表锁,行锁

 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查出结果

mysql - 表锁,行锁

 key1已经变成了0

从而避免了库存超卖。

死锁:

和java的差不多。。。

a:查询A,上锁A, 等查到后修改B,  然后提交

b:查询B,上锁B, 等查到后修改A,  然后提交

对于a,需要修改B才能提交事务,然后解锁A。

对于b,需要修改A才能提交事务,然后解锁B。

形成死锁。

彼此占用钥匙。

解决:kill掉其中一个线程。(图形界面比如sqlyog中直接点个x就完事了。。。不过linux开发有时要用名命令行的那种 - -)

相关推荐