如果没有锁,那么并发性会更强,但是数据安全性会有问题。因此数据库会给数据加锁。
也就是读写锁,共享锁可以叠加共享锁但是不能加排他锁, 排他锁则不能叠加。 根据隔离级别等等,mysql会隐式的给数据自动加锁 此外还可以使用share in model, for update 等语句显示的加锁
粒度越细,维护锁的开销越大,并发性越高,数据越不安全。 通常有如下两种
- 行锁
只给一行数据加锁
- 表锁
给整张表加锁, 一般Alter table会使用表锁
mysql为了提高冲突监测性能而存在的一种锁。是给其上一级所加的锁,所以在mysql中通常为表锁。
比如一个事务,给两行加了排他锁,又有一个事务想要给整个表加共享锁,这个时候就要去查看所有的表的行是否有锁策略不能加共享锁。 如果有了意向锁,那么事务1就能再给两行加排他锁的同时,给整个表加IX, 这样事务2不用遍历就能知道不可以加S锁了。
A,B两个人同时在系统上进行操作,比如要修改一份订单。 当A正在修改准备提交前向上厕所,这儿时候B修改完订单并且提交了。 这个时候A回来了再提交就会出现问题,B的修改被覆盖了。
- 悲观锁
for update, share in model语句
使用悲观锁,则会在操作期间全程对数据进行加锁,其他人不能再次修改。这样会造成比较长时间的阻塞。
适用于短事务,写量比较大的情况。
- 乐观锁
乐观锁通过在数据库中新增一个version字段,操作后给version + 1. 提交时比较,如果比数据库中的大,则提交。大概的代码是:
<code class="hljs scss"> if(connection.update("update set name='123' where id=1 and version < #current_version#") > 0){ // 表示更新成功了 }</code>
<code class="hljs scss">这种适用于读多,写少,并且写有可能会用很长时间的情况。 </code>
死锁
比如有两个事物:<br />
事物1
<code class="hljs scss"><code class="hljs sql">update table1 set name='1' where id=1; // sleep 1 update table1 set name='2' where id=2; commit;</code></code>
<code class="hljs sql">事物2
<code class="hljs scss"><code class="hljs sql"><code class="hljs sql">update table1 set name='2' where id=2; // sleep 2 update table1 set name='1' where id=1; commit;</code></code></code>
<code class="hljs sql"><code class="hljs sql">上面的语句如果恰巧一起执行到sleep1 和sleep2,那么就会造成死锁。 一般有三种做法解决:
<code class="hljs sql"><code class="hljs sql">一次锁所有数据 保持锁的顺序 允许死锁,然后kill掉代价最小的事务。回滚之
<code class="hljs sql"><code class="hljs sql">间隙锁
<code class="hljs scss"><code class="hljs sql"><code class="hljs sql">执行select .. from where id between这样的语句的时候锁定这一区间,不能插入或者删除数据,以防止幻读。 </code></code></code>
<code class="hljs sql"><code class="hljs sql">MVCC 多版本并发控制
<code class="hljs sql"><code class="hljs sql">mysql维护一个系统版本号,每次有新的事物开始的时候递增。<br />
每行后面保存两个隐藏列。 一个创建时版本号C,一个删除时版本号D
<code class="hljs sql"><code class="hljs sql"><strong>INSERT</strong> 新增的行往C里写入当前系统版本号。 这样新事物可以通过这个版本号保证不查到他 <strong>DELETE</strong> 为删除的行写D字段为当前系统版本号。 <strong>UPDATE</strong> 插入一行新数据写C为当前系统版本号,老数据写D为当前系统版本号。 <strong>SELECT</strong> 的时候只查 C<=当前版本 && (D > 当前版本 || D is not defined) 这样主要是为了在不加锁的情况下,一个事务能够读取到事务开始前已经存在且未被删除,且没有经过修改的数据。 也就是解决脏读的问题。即该事物开始之后的其他事务的各种修改事务提交都不会对当前事务的读取产生影响。同时也解决了幻读的问题
<code class="hljs sql"><code class="hljs sql">语句与锁
<code class="hljs sql"><code class="hljs sql">InnoDB 的行锁是基于索引的,如果没有索引,或者不能使用索引则是表锁 select … from 一致性非阻塞读,不上锁。 select … from where lock in share mode 共享锁 select … from where … for update 排他锁 update .. where 排他锁 delete … from 排他锁<br />
下面的例子来看锁的排他性<br />
事务1
<code class="hljs scss"><code class="hljs sql"><code class="hljs sql"><code class="hljs sql"> set autocommit=0; begin; SELECT * FROM biz_pay_task where id = 1 FOR UPDATE; // wait commit;</code></code></code></code>
<code class="hljs sql"><code class="hljs sql"><code class="hljs sql">事务2
<code class="hljs scss"><code class="hljs sql"><code class="hljs sql"><code class="hljs sql"><code class="hljs sql"> set autocommit=0; begin; SELECT * FROM biz_pay_task where id = 1 LOCK IN SHARE MODE; commit;</code></code></code></code></code>
<code class="hljs sql"><code class="hljs sql"><code class="hljs sql">事务2在执行SELECT LOCK IN SHARE MODE的时候会阻塞,知道事务1commit之后才会完成。