锁的机制是为了保证在并发控制时,多个事务对数据库进行操作时,保证数据正确符合预期,保证事务的隔离一致性。
乐观并发控制(乐观锁),悲观并发控制(悲观锁)只是一种设计思想,并非某一种工具或数据库的实现,该思想在memcached、redis中也有应用。
举一个在开发过程中的例子:
老师批改作业编辑评语得分,因为多个老师同时对学生提交的作业列表进行批改,完成后保存。
乐观锁:
预期老师很少会有批改冲突
1.在数据库 作业加一个version的字段 默认0
2.一个老师1点击进入作业(获得version=0),完成批改点击提交(查询数据库 如果此时version=0)则提交保存修改,同时版本号veruin+1
3.若老师2在提交之前,老师2也进入该页面(也获取version=0),并完成提交(version=1)
4.此时老师1再进行保存提交时,发现数据库version与本页面保存的version不一致,则提示禁止提交,退出重试即可
以上过程即实现了一个简单的乐观锁机制。可见乐观锁与数据库定义的行锁、表锁等不是一类东西
乐观锁开销小,由程序实现不会产生死锁,但是仍存在冲突,两个事务几乎同时都读到了数据的某一行,存在此风险,在某些需要大量编辑操作的页面,在客户已经进行了大量编辑后返回其他页面也是难以接受的。
悲观锁:
预期冲突非常多,锁争用严重。需要借助数据库锁的实现,因为程序中即使加上验证,但是不能保证外部程序进行并发操作。
在实现上就是利用数据库自身的排它锁
在老师1点击进入作业时,完成后点击提交对所涉及的数据申请排它锁,若失败说明该数据正在被使用,则提示老师稍后处理,若获加锁成功,则在事务完成后释放锁(在锁释放前若老师对同一个对象进行操作,则数据操作,可能会等待或派出异常,具体实现由程序自定义)
需要注意的是,要实现悲观锁需要关闭Mysql数据库的自动提交功能(默认autocommit)
//1.开始事务
begin;/begin work;/start transaction; (三者选一就可以)
//2.查询出商品信息
select status from t_goods where id=1 for update;
//3.根据商品信息生成订单
insert into t_orders (id,goods_id) values (null,1);
//4.修改商品status为2
update t_goods set status=2;
//5.提交事务
commit;/commit work;
其中第2步 查询语句最后的 for update 即为加锁语句(开启排它锁) 此时id-1的数据被锁定,其他事务就必须在该事务提交之后才能操作
同时Mysql的InnoDB引擎的排他锁默认是行级锁,行级锁依赖于索引,若sql没有用到索引,则会将整个表都加锁。
这种先去锁在访问的原则保证了数据安全性,但增加了开销,设计不好还可能造成死锁。不要在只读事务中出现。
补充:
共享锁: select 。。。。 lock in share mode 对查询到的数据每行都加锁,其他事务只能读,不能修改
意向锁:表级锁,是事务准备对下一行准备加锁的声明。InnoDB 有两种意向锁
1.意向共享锁(IS):表示事务准备给数据行加入共享锁,也就是说一个数据行加共享锁前必须先取得该表的IS锁
2.意向排它锁(IX):表示事务准备给数据行加入排他锁,说明事务在一个数据行加排他锁前必须先取得该表的IX锁。
意向锁是数据库自动完成,不需要程序在进行操作
对于insert、update、delete,InnoDB会自动给涉及的数据加排他锁(X);对于一般的Select语句,InnoDB不会加任何锁,事务可以通过以下语句给显示加共享锁或排他锁。
共享锁:SELECT ... LOCK IN SHARE MODE;
排他锁:SELECT ... FOR UPDATE;
综上可知
当我们在进行 插入 修改 删除 sql操作时, 查询条件一定要使用索引字段,最好使用主键,如果没用到索引 则排它锁会将整张表锁住,其他的并发查询sql也必须等待。。。。