数据库锁
- 表锁:开销小,加锁快;不会出现死锁;锁定力度大,发生锁冲突概率高,并发度最低
- 行锁:开销大,加锁慢;会出现死锁;锁定粒度小,发生锁冲突的概率低,并发度高
- InnoDB行锁和表锁都支持!InnoDB只有通过索引条件检索数据才使用行级锁
- MyISAM只支持表锁!
MyISAM表级锁
MySQL 的表级锁有两种模式:表共享读锁、表独占写锁
读读不阻塞,读写阻塞,写写阻塞
读锁和写锁是互斥的,读写操作是串行
对 MyISAM 表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;对 MyISAM 表的写操作,则会阻塞其他用户对同一表的读和写操作。当一个线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。
session_1 | session_2 |
---|---|
获得text表的锁定: mysql> lock table text write; |
略 |
当前 session 对text表的查询、更新、插入操作都可以执行 | session2对于text表的查询、更新、插入操作都得阻塞等待 |
释放锁: unlock tables; |
等待 |
session_2的操作得到执行 |
如何给MyISAM加表锁
MyISAM 在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT 等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此,用户一般不需要直接用 LOCK TABLE 命令给 MyISAM 表显式加锁。
但有些特殊需求下需要我们手动加表锁。举个例子:例如订单表记录了订单的总金额,订单详细表记录了订单中各个商品的价格,现在需要比对订单总金额跟订单详细表金额的总价是否正确。
select sum(total) from orders;
select sum(subtotal) from order_detail;
比对 toatal 和 total_deail
这时候就需要手动将order,order_detail手动加锁,不然查询时又有新的记录插入会导致比对不一致。
Lock tables orders read local, order_detail read local;
Select sum(total) from orders;
Select sum(subtotal) from order_detail;
Unlock tables;
注意点:
- 在执行 LOCK TABLES 后,只能访问显式加锁的这些表,不能访问未加锁的表
- 如果加的是读锁,那么只能执行查询操作,而不能执行更新操作
- 写锁和读锁优先级的问题是可以通过参数调节的:max_write_lock_count和low-priority-updates
- MyISAM可以支持查询和插入操作的并发进行。可以通过系统变量concurrent_insert来指定哪种模式,在MyISAM中它默认是:如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录。
MyISAM 的锁调度
MyISAM 存储引擎的读锁和写锁是互斥的,读写操作是串行的。那么,一个进程请求某个 MyISAM 表的读锁,同时另一个进程也请求同一表的写锁,MySQL 如何处理呢?
写进程先获得锁。不仅如此,即使读请求先到锁等待队列,写请求后到,写锁也会插到读锁请求之前
Innodb行锁
InnoDB实现了以下两种类型的行锁
- 共享锁(S锁):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁
(也叫做读锁:读锁是共享的,多个客户可以同时读取同一个资源,但不允许其他客户修改) - 排他锁(X锁) :允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁
(也叫做写锁:写锁是排他的,写锁会阻塞其他的写锁和读锁)
另外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB 还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁
- 意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的 IS 锁
-
意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的 IX 锁
意向锁是 InnoDB 自动加的,不需用户干预。对于 UPDATE、DELETE 和 INSERT 语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通 SELECT 语句,InnoDB 不会加任何锁;事务可以通过以下语句显示给记录集加共享锁或排他锁:
- 共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
- 排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE
只有通过索引条件检索数据,InnoDB 才使用行级锁,否则,InnoDB 将使用表锁
session1 | session2 |
---|---|
mysql> select * from tab_no_index where id = 1 for update; | |
mysql> select * from tab_no_index where id = 2 for update; 阻塞等待 |
这里由于tab_on_index表没有加索引,所以即使是为id=1的加锁,也是用到的不是行锁而是表锁,所以导致session2查询时阻塞等待
由于 MySQL 的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。
当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB 都会使用行锁来对数据加锁
间隙锁
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB 会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB 也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(Next-Key 锁)。