赞
踩
Mysql在5.5之前默认使用MyISAM存储引擎,之后使用InnoDB。查看当前存储引擎:
show variables like ‘%storage_engine%’;
MyISAM操作数据都是使用的表锁,你更新一条记录就要锁整个表,导致性能较低,并发不高。当然它也不会存在死锁问题。
InnoDB与M有ISAM的最大不同有两点:
- InnoDB支持事务
- InnoDB采用了行级锁。也就是你需要修改哪行,就可以只锁定哪行。
在Mysql中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql语句操作了主键索引,Mysql就会锁定这条主键索引;如果一条语句操作了非主键索引,Mysql会先锁定该非主键索引,再锁定相关的主键索引。
InnoDB行锁是通过给索引加锁实现的,如果没有索引,InnoDB会通过因此的聚簇索引来对记录加锁。也就是说:如果不通过索引条件检索数据,那么InnoDB将对表中所有数据加锁,实际效果跟表锁一样。因为没有了索引,找到记录就得扫描全表,要扫描全表,就得锁定表。
首先说明:InnoDB引擎默认对update,delete,insert加排它锁,select语句默认不加锁
共享锁shared locks(S锁)也称读锁:用于不更改或不更新数据的操作(只读操作),可以查看但无法修改和删除的一种数据锁,如select语句。
如果事务T对数据A加上共享锁后,则其他事务只能对数据A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。共享锁下其它用户可以并发读取,查询数据。但不能修改,增加,删除数据。资源共享。
select ... lock in share mode
- 对于使用共享锁的事务,其他事务只能读,不可写
- 如果执行了更新操作则会一直等待,直到当前事务commit或者rollback
- 如果当前事务也执行了其他事务处于等待的那条sql语句,当前事务将会执行成功,而其他事务会报死锁
- 允许其他锁共存
排它锁Exclusive Locks(X锁)也称写锁、独占锁:用于数据修改操作,例如insert、update或delete。确保不会同时对同一资源进行多重更新。
如果事务T对数据A加上排它锁后,则其他事务不能在对A加任何类型的锁。获准排他锁的事务既能读数据,又能修改数据。我们在操作数据库的时候,可能会由于并发问题而引起的数据的不一致性(数据冲突)
select ... for update
**for update:**InnoDB默认是行级别的锁,当有明确指定的主键是,使用的是行锁;否则使用表锁。使用情况详细如下:
明确指定主键,并且存在此记录,行级锁。例如:
-- id是主键
select name,age from table_user where id = '1' for update;
明确指定主键,若查无记录,无锁。例如:
-- id是主键,单不存在id = 1的数据
select name,age from table_user where id = '1' for update;
无主键,表级锁。例如:
-- age是普通字段
select name,age from table_user where age = 12 for update;
主键不明确,表级锁。例如:
-- id是主键,age不是,但数据库
select name,age from table_user where age = 12 and id = '1' for update;
- 对于排它锁的事务,其它事务可读,但不可进行更新操作
- for update仅使用与InnoDB,并且必须开启事务,在begin和commit之间才生效
- 当一个事务进行for update的时候,另一个事务也有for update时会一直等待,直到之前的事务commit或rollback或断开连接释放锁才能拿到锁进行后面的操作(排它锁不能共存)
- InnoDB引擎默认对update,delete,insert加排它锁,select语句默认不加锁
- 加过排他锁的数据行在其他事务中是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select … from …查询数据,因为普通查询没有任何锁机制
首先说明:乐观锁和悲观锁都是针对读(select)来说的。
乐观锁不是数据库自带的,需要我们自己去实现。乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。
悲观锁就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作,这点跟Java中的synchronized很相似,所以悲观锁需要耗费较多的时间。另外与乐观锁相对应,悲观锁是由数据库自己实现了的,要用的时候,我们直接调用数据库的相关语句就可以了。
由悲观锁涉及到的另外两个锁概念,就是共享锁与排他锁。共享锁和排他锁是悲观锁的不同实现,它俩都属于悲观锁的范畴。
某商品,用户购买后库存应-1,而某两个或多个用户同时购买,此时三个执行程序均同时读得库存为“n”,之后进行了一些操作,最后将均执行update table set 库存书 = n - 1,那么,很显然这是错误的。
解决:
使用悲观锁(也就是排他锁)
- 程序A在查询库存数时使用排他锁(select * from table where id = 10 for update)
- 然后进行后续操作,包括更新库存数,最后提交事务
- 程序B在查询库存数时,如果A还未释放排他锁,程序B将等待。。。
- 程序C同B。。。
使用乐观锁(靠表设计和代码来实现)
一般是在该商品表添加version版本字段或者timestamp时间戳字段
程序A查询后,执行更新变成了:
update table set num = num - 1 and version = 23
这样,保证了修改的数据是和它查询出来的数据是一致的(其他执行程序肯定未进行修改)。当然,如果更新失败,表示在更新操作之前,有其他执行程序已经更新了该库存数,那么就可以尝试重试来保证更新成功。为了尽可能避免更新失败,可以合理调整重试次数(阿里巴巴开发手册规定重试次数不低于三次)。
悲观锁实际使用了排他锁来实现**(select … for update)**。InnoDB加行锁的前提是:必须是通过索引条件来检索数据,否则会切换为表锁。
因此,悲观锁在未通过索引条件检索数据时,会锁定整张表。导致其他程序不允许**“加锁的查询操作”**,影响吞吐。因此,如果在查询居多的情况下,推荐使用乐观锁。
加锁的查询操作:加过排他锁的数据行在其他事务中是不能修改的,也不能通过for update或lock in share mode的加锁方式查询,但可以直接通过select … from …查询数据,因为普通查询没有任何锁机制。
乐观锁更新有可能会失败,甚至是更新几次都失败,这是有风险的。所以如果写入居多,对吞吐要求不高,可使用悲观锁。
结尾:读用乐观锁,写用悲观锁。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。