http://www.javashuo.com/article/p-msbhlrah-hv.htmlhtml
MySQL大体可概括为如下3种锁:数据库
- 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的几率最高,并发度最低。
- 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的几率最低,并发度也最高。
- 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度通常
MySQL表级锁的锁模式(MyISAM)
- MySQL表级锁有两种模式:表共享锁(Table Read Lock)和表独占写锁(Table Write Lock)。
-
LOCK tables orders
read
local
,order_detail
read
local
;
并发
SELECT
SUM
(total)
FROM
orders;
spa
SELECT
SUM
(subtotal)
FROM
order_detail;
线程
Unlock tables;
code
MyISAM存储引擎有一个系统变量concurrent_insert,专门用以控制其并发插入的行为,其值分别能够为0、1或2。htm
- 当concurrent_insert设置为0时,不容许并发插入。
- 当concurrent_insert设置为1时,若是MyISAM容许在一个读表的同时,另外一个进程从表尾插入记录。这也是MySQL的默认设置。
- 当concurrent_insert设置为2时,不管MyISAM表中有没有空洞,都容许在表尾插入记录,都容许在表尾并发插入记录。
MyISAM的锁调度blog
InnoDB锁问题
- InnoDB与MyISAM的最大不一样有两点:
- 一是支持事务(TRANSACTION);
- 二是采用了行级锁。
事务(Transaction)及其ACID属性
并发事务带来的问题排序
- 更新丢失(Lost Update):
- 脏读(Dirty Reads):
- 不可重复读(Non-Repeatable Reads):
- 一个事务在读取某些数据已经发生了改变、或某些记录已经被删除了!这种现象叫作“不可重复读”。
- 幻读(Phantom Reads):
- 一个事务按相同的查询条件从新读取之前检索过的数据,
- 却发现其余事务插入了知足其查询条件的新数据,这种现象就称为“幻读”。
数据库实现事务隔离的方式,基本能够分为如下两种。索引
隔离级别/读数据一致性及容许的并发反作用 |
读数据一致性 |
脏读 |
不可重复读 |
幻读 |
未提交读(Read uncommitted) |
最低级别,只能保证不读取物理上损坏的数据 |
是 |
是 |
是 |
已提交度(Read committed) |
语句级 |
否 |
是 |
是 |
可重复读(Repeatable read) |
事务级 |
否 |
否 |
是 |
可序列化(Serializable) |
最高级别,事务级 |
否 |
否 |
否 |
InnoDB实现了如下两种类型的行锁。
- 共享锁(s):容许一个事务去读一行,
- 排他锁(X):容许获取排他锁的事务更新数据,
-
另外,为了容许行锁和表锁共存,实现多粒度锁机制,
InnoDB行锁实现方式
- InnoDB行锁是经过索引上的索引项来实现的,
- 这一点MySQL与Oracle不一样,
- InnoDB这种行锁实现特色意味者:
- 只有经过索引条件检索数据,InnoDB才会使用行级锁,
- 不然,InnoDB将使用表锁!
间隙锁(Next-Key锁)
- 当咱们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,
- 对于键值在条件范围内但并不存在的记录,叫作“间隙(GAP)”,
- InnoDB也会对这个“间隙”加锁,这种锁机制不是所谓的间隙锁(Next-Key锁)。
何时使用表锁
- 对于InnoDB表,在绝大部分状况下都应该使用行级锁,
- 由于事务和行锁每每是咱们之因此选择InnoDB表的理由。
- 但在个另特殊事务中,也能够考虑使用表级锁。
- 第一种状况是:事务须要更新大部分或所有数据,表又比较大,
- 若是使用默认的行锁,不只这个事务执行效率低,
- 并且可能形成其余事务长时间锁等待和锁冲突,
- 这种状况下能够考虑使用表锁来提升该事务的执行速度。
- 第二种状况是:事务涉及多个表,比较复杂,极可能引发死锁,形成大量事务回滚。
- 这种状况也能够考虑一次性锁定事务涉及的表,
- 从而避免死锁、减小数据库因事务回滚带来的开销。
- 固然,应用中这两种事务不能太多,不然,就应该考虑使用MyISAM表。
- 在InnoDB下 ,使用表锁要注意如下两点。
- (1)使用LOCK TALBES虽然能够给InnoDB加表级锁,
- 但必须说明的是,表锁不是由InnoDB存储引擎层管理的,
- 而是由其上一层MySQL Server负责的,
- 仅当autocommit=0、innodb_table_lock=1(默认设置)时,
- InnoDB层才能知道MySQL加的表锁,MySQL Server才能感知InnoDB加的行锁,
- 这种状况下,InnoDB才能自动识别涉及表级锁的死锁;
- 不然,InnoDB将没法自动检测并处理这种死锁。
- (2)在用LOCAK TABLES对InnoDB锁时要注意,要将AUTOCOMMIT设为0,不然MySQL不会给表加锁;
- 事务结束前,不要用UNLOCAK TABLES释放表锁,由于UNLOCK TABLES会隐含地提交事务;
- COMMIT或ROLLBACK产不能释放用LOCAK TABLES加的表级锁,必须用UNLOCK TABLES释放表锁。
-
SET
AUTOCOMMIT=0;
LOCAK TABLES t1 WRITE, t2
READ
, ...;
[do something
with
tables t1
and
here];
COMMIT
;
UNLOCK TABLES;
关于死锁
- MyISAM表锁是deadlock free的
- 可是在InnoDB中,除单个SQL组成的事务外,锁是逐步得到的,这就决定了InnoDB发生死锁是可能的。
- 发生死锁后,InnoDB通常都能自动检测到,并使一个事务释放锁并退回,另外一个事务得到锁,继续完成事务。
- 但在涉及外部锁,或涉及锁的状况下,InnoDB并不能彻底自动检测到死锁,
- 这须要经过设置锁等待超时参数innodb_lock_wait_timeout来解决。
下面就经过实例来介绍几种死锁的经常使用方法。
- (1)在应用中,若是不一样的程序会并发存取多个表,
- 应尽可能约定以相同的顺序为访问表,这样能够大大下降产生死锁的机会。
- (2)在程序以批量方式处理数据的时候,
- 若是事先对数据排序,保证每一个线程按固定的顺序来处理记录,也能够大大下降死锁的可能。
- (3)在事务中,若是要更新记录,应该直接申请足够级别的锁,即排他锁,
- 而不该该先申请共享锁,更新时再申请排他锁,甚至死锁。
- (4)在REPEATEABLE-READ隔离级别下,
- 若是两个线程同时对相同条件记录用SELECT...ROR UPDATE加排他锁,
- 程序发现记录尚不存在,就试图插入一条新记录,若是两个线程都这么作,就会出现死锁。
- 这种状况下,将隔离级别改为READ COMMITTED,就能够避免问题。
- (5)当隔离级别为READ COMMITED时,
- 若是两个线程都先执行SELECT...FOR UPDATE,判断是否存在符合条件的记录,若是没有,就插入记录。
- 此时,只有一个线程能插入成功,另外一个线程会出现锁等待,
- 当第1个线程提交后,第2个线程会因主键重出错,但虽然这个线程出错了,却会得到一个排他锁!
- 这时若是有第3个线程又来申请排他锁,也会出现死锁。
- 对于这种状况,能够直接作插入操做,而后再捕获主键重异常,或者在遇到主键重错误时,老是执行ROLLBACK释放得到的排他锁。