1.数据库锁
在数据库的四个特性ACID中,要想保持隔离性,在修改同一份数据的情况下,两个事务必须挨个执行以免出现冲突情况。而数据库有四种隔离级别(注意:不是所有数据库支持所有隔离级别)
隔离级别 | 脏读(Dirty Read) | 不可重复读(NonRepeatable Read) | 幻读(Phantom Read) |
---|---|---|---|
未提交读 (Read uncommitted) | 可能 | 可能 | 可能 |
已提交读(Read committed) | 不可能 | 可能 | 可能 |
可重复读(Repeatable read) | 不可能 | 不可能 | 可能 |
可串行化(Serializable ) | 不可能 | 不可能 | 不可能 |
其中大多数数据库的默认隔离级别是Read committed。
Dirty reads:A事务可以读到B事务还未提交的数据。
Non-repeatable read:A事务读取一行数据,B事务后续修改了这行数据,A事务再次读取这行数据,结果得到的数据不同。
Phantom reads:A事务通过SELECT … WHERE得到一些行,B事务插入新行或者更新已有的行使得这些行满足A事务的WHERE条件,A事务再次SELECT … WHERE结果比上一次多了一些行。
关于不可重复读和幻读的区别,不可重复读重点在于update,而幻读的重点在于insert,和delete。二者最大的区别可以理解为如何通过锁机制来解决他们产生的问题。
2. 行级锁:排它锁和共享锁
排它锁,写锁:如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。
在查询语句后面增加FOR UPDATE,Mysql会对查询结果中的每行都加排他锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请排他锁,否则会被阻塞。
共享锁,读锁:其他用户可以并发读取数据,但任何事务都不能对数据进行修改,如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。
在查询语句后面增加LOCK IN SHARE MODE,Mysql会对查询结果中的每行都加共享锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请共享锁,否则会被阻塞。其他线程也可以读取使用了共享锁的表,而且这些线程读取的是同一个版本的数据。
表级锁在MyISAM和innoDB中都有用到,创建锁的开销小,不会出现死锁,由于锁定的是整张表,所以并发度低。当需要频繁对大部分数据做 GROUP BY 操作或者需要频繁扫描整个表时,推荐使用表级锁。
3. 表级锁:读锁和写锁
读锁
用法:LOCK TABLE table_name [ AS alias_name ] READ
释放锁使用UNLOCK tables.可以为表使用别名,如果一旦使用别名在使用的时候也必须采用别名。成功申请读锁的前提是当前没有线程对该表使用写锁,否则该语句会被阻塞。申请读锁成功后,其他线程也可以对该表进行读操作,但不允许有线程对其进行写操作,就算是当前线程也不允许。当锁住了A表之后,就只能对A表进行读操作,对其他表进行读操作会出现错误(tablename was not locked with LOCK TABLES)
写锁
用法: LOCK TABLE table_name [AS alias_name] [ LOW_PRIORITY ] WRITE
同样也可以使用别名,与读锁不同的是,写锁中可以指定锁的优先级。LOW_PRIORITY是一种比读锁更低优先级的锁,当多个线程同时申请多种锁(LOW_PRIORITY,READ,WRITE)时,LOW_PRIORITY的优先级最低。读锁申请成功的前提是没有线程对表加读锁和其他写锁,否则会被阻塞。
4. 乐观锁和悲观锁:
乐观锁:认为操作不会产生并发问题(不会有其他线程对数据进行修改)。
实现方法:
- 利用版本号
取出记录时,获取当前version
更新时,带上这个version
执行更新时, set version = newVersion where version = oldVersion
如果version不对,就更新失败 - 加入时间戳,在表中增加一个字段,更新提交时进行时间戳对比,一致就OK,否则就是版本冲突。
悲观锁:每次取数据时都认为其他线程会修改,所以都会加(悲观)锁。
应用实例:MySQL的读锁、写锁、行锁等
Java的synchronized关键字