在说具体的锁结构时,先思考一个问题,那就是为什么要上锁?然后我要如何选择锁?锁具体如何实现?
在文章得末尾我给出了我的个人答案。
一、什么是悲观锁?
1、悲观锁就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作,这点跟java中的synchronized很相似。
2、在MySQL中如何实现悲观锁。?
mysql中有悲观锁的实现,我们想实现悲观锁时调用相对应得语句。
测试用表的结构和插入一行数据,下面其他的锁也会同时用到这个表。
use test; create table msq_test ( id int primary key, status ) engine = innodb default character set = 'utf8'; , '1');
操作:1、set autocommit=0; 2、select .....for update实现锁
注意:要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交。我们可以使用命令设置MySQL为非autocommit模式:set autocommit=0;
//开始事务 begin; //查询出主键id=1的信息 for update; //修改status为2 ; //提交事务 commit;
注:上面的begin/commit为事务的开始和结束,因为在前一步我们关闭了mysql的autocommit,所以需要手动控制事务的提交
在这里,我们使用了select…for update的方式,这样就通过数据库实现了悲观锁。此时在msq_test表中,id为1的 那条数据就被我们锁定了,其他事务的操作必须等待我们自己主动commit提交事务之后才能操作,这样我们可以保证当前的数据不会被其它事务修改。
二、什么是乐观锁 ?
1、乐观锁就是每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
2、在mysql如何实现乐观锁?
乐观锁不是数据库自带的,需要我们自己去实现
use test; create table msq_test ( d int primary key, status ), version not null ) engine = innodb default character set = 'utf8'; , );
在上表中添加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。
;//得出一开始设置得version字段(versionValue) where version = versionValue;
这样子就实现了乐观锁机制。
三、什么是行锁和表锁?
行锁:
行锁,由字面意思理解,就是给某一行加上锁,也就是一条记录加上锁。
" lock in share mode;
在msq_test表中,id字段为主键,就也相当于索引。执行加锁时,会将id这个索引为1的记录加上锁,那么这个锁就是行锁。
到这里,我一开始也是很懵逼,啥是 lock in share mode???
select.....lock in share mode走的是IS锁(意向共享锁),即在符合条件的rows(行)上都加了共享锁,这样的话,其他session(事务)可以读取这些记录,也可以继续添加IS锁,但是无法修改这些记录直到你这个加锁的session执行完成(否则直接锁等待超时)。
表锁:
根据行锁,你能理解啥是表锁吗?我想你智商比我高,应该可以。
不过我们需要注意一些锁的级别,MySQL InnoDB默认Row-Level Lock,所以只有「明确」地指定主键,MySQL 才会执行Row lock (只锁住被选取的数据) ,否则MySQL 将会执行Table Lock (将整个数据表单给锁住)。
四、排他锁
1、什么是排他锁
若事务T对数据对象A加上X锁,则只允许T读取和修改A,其他任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。这就保证了其他事务在T释放A上的锁之前不能再读取和修改A。
啥?这定义太过复杂,难道买菜我会需要用高数去计算菜钱?我也觉得没必要。所以可以认为上了排他锁,其他线程既不能读也不能修改。
2、如何用排他锁
用法: select … for update;
例如:select * from msq_test where id = 1 for update;
排他锁的申请前提:没有线程对该结果集中的任何行数据使用排他锁或共享锁,否则申请会阻塞。
for update仅适用于InnoDB,且必须在事务块(BEGIN/COMMIT)中才能生效。在进行事务操作时,通过“for update”语句,MySQL会对查询结果集中每行数据都添加排他锁,其他线程对该记录的更新与删除操作都会阻塞。排他锁包含行锁、表锁。
注意:
事务保证整个操作的成一个组,要么全做要么全不做 但是不能保证多个事务同时读取同一个数据
数据对象被加上排它锁时,其他的事务不能对它读取和修改;加了共享锁的数据对象可以被其他事务读取,但不能修改
文章的末尾,回到一开始的问题,为何要上锁?锁的具体实现上文已经给出。
答:为何上锁?
事务可以用锁实现,可以保证一致性和隔离性,但是锁用来保证并发性;但是隔离性只是保证不会出现相互读取中间数据(却无法解决并发的问题)
为啥保持一致性的原因: MySQL会出现丢失更新:一个事务的更新覆盖了其它事务的更新结果,就是所谓的更新丢失。例如:用户A把值从11改为8,用户B把值从8改为11,则用户A丢失了他的更新。
为啥保持隔离性的原因:MySQL会出现脏读:当一个事务读取其它完成一半事务的记录时,就会发生脏读取。例如:用户A,B看到的值都是9,用户B把值改为5,用户A读到的值仍为9。