mysql锁机制整理

Auth: jin
Date: 20140506

主要参考整理资料
MYSQL性能调优与架构设计-第七章 MYSQL锁定机制
http://www.cnblogs.com/ggjucheng/archive/2012/11/14/2770445.html
理解MySQL——架构与概念
http://www.cnblogs.com/yequan/archive/2009/12/24/1631703.html

一、mysql锁类型及应用介绍
1.锁类型和应用类型
相對其他數據庫而言,MySQL的鎖機制比較簡單,其最顯著的特點是不同的存儲引擎支持不同的鎖機制,但總的來説,
mysql各種存儲引擎使用了三種類型的鎖定機制:行級鎖定、頁級鎖定和表級鎖定。其中,MyISAM主要使用表級鎖定,
而使用行級鎖定的主要是Innodb,BDB使用頁級鎖
①表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的概率最高,并發度最低。MyISAM
②行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,并發度也最高。Innodb NDB
③頁級鎖:開銷和加鎖時間界于表鎖和行鎖之間;會出現死鎖;鎖定粒度界于表鎖和行鎖之間,并發度一般BDB

每种锁都是按各自的应用场景而优化设计的。表锁可能适合web应用(读多写少);而行级锁可能更适合OLTP系统。

联机事务处理OLTP和联机分析处理OLAP介绍
当今的数据处理大致可以分成两大类:联机事务处理OLTP(on-line transaction processing)、联机分析处理OLAP(On-Line Analytical Processing)。
OLAP是数据仓库系统的主要应用,支持复杂的分析操作,侧重决策支持,并且提供直观易懂的查询结果。
OLTP是传统的关系型数据库的主要应用,主要是基本的、日常的事务处理,例如银行交易。
联机事务处理系统(OLTP)也称为面向交易的处理系统,其基本特征是顾客的原始数据可以立即传送到计算中心进行处理,并在很短的时间内给出处理结果。
OLTP 系统旨在处理同时输入的成百上千的事务。实时性要求高。数据量不是很大。交易一般是确定的,所以OLTP是对确定性的数据进行存取。)

二、表级锁
(一)机制
适用于web应用(读多写少)
MyISAM 存储引擎使用的锁定机制完全是由 MySQL 提供的表级锁定实现。
mysql的表级锁定主要有两种:写锁和读锁(只能读)
对write写锁,MySQL使用的表锁定方法原理如下:
* 如果在表上没有锁,在它上面放一个写锁。
* 否则,把锁定请求放在写锁定队列中。
对read读锁,MySQL使用的表锁定方法原理如下:
* 如果在表上没有写锁定,把一个读锁定放在它上面。
* 否则,把锁请求放在读锁定队列中。
当一个锁定被释放时,锁定可被写锁定队列中的线程得到,然后是读锁定队列中的线程。这意味着,如果你在一个表上有许多更新,SELECT语句将等待直到没有更多的更新。

在 MySQL 中,主要通过四个队列来维护这两种锁定:两个存放当前正在锁定中的读和写锁定信息,另外两个存放等待中的读写锁定信息,如下:
Current read-lock queue (lock->read)
Pending read-lock queue (lock->read_wait)
Current write-lock queue (lock->write)
Pending write-lock queue (lock->write_wait)

当客户端请求写锁时,mysql首先检查在Current write-lock queue是否已经有锁定相同资源到信息存在,如果Current write-lock queue没有,则再检查Pending write-lock queue ,
如果在Pending write-lock queue 中找到了,则自己也需要进入该等待队列;反之,如果在Pending write-lock queue 找不到,则再检测Current read-lock queue,
如果有锁定存在,则同样需要进入Pending write-lock queue。
如果一开始就检测到Current write-lock queue中有锁定相同资源的写锁存在,那么就直接进入Pending write-lock queue。

读请求和写等待队列中的写锁请求的优先级规则主要为以下规则决定:
1. 除了 READ_HIGH_PRIORITY 的读锁定之外,Pending write-lock queue 中的 WRITE 写锁定能够阻塞所有其他的读锁定;
2. READ_HIGH_PRIORITY 读锁定的请求能够阻塞所有 Pending write-lock queue 中的写锁定;
3. 除了 WRITE 写锁定之外,Pending write-lock queue 中的其他任何写锁定都比读锁定的优先级低。

表级锁在下列几种情况下比行级锁更优越:
1. 很多操作都是读表。
2. 在严格条件的索引上读取和更新,当更新或者删除可以用单独的索引来读取得到时:
3. UPDATE tbl_name SET column=value WHERE unique_key_col=key_value;
4. DELETE FROM tbl_name WHERE unique_key_col=key_value;
5. SELECT 和 INSERT 语句并发的执行,但是只有很少的 UPDATE 和 DELETE 语句。
6. 很多的扫描表和对全表的 GROUP BY 操作,但是没有任何写表。

(二).查看系统锁状态
可以通过检查table_locks_waited和table_locks_immediate状态变量来分析系统上的表锁定争夺:
mysql>show global status like 'table%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Table_locks_immediate | 34 |
| Table_locks_waited | 0 |
+-----------------------+-------+
2 rows in set (0.00 sec)
Table_locks_immediate 表示立即释放表锁数
Table_locks_waited 表示需要等待的表锁数,
Table_locks_waited :
The number of times that a request for a table lock could not be granted immediately and a wait was needed.
If this is high and you have performance problems, you should first optimize your queries, and then either split your table or tables or use replication.
应该关心Table_locks_waited的值
如果Table_locks_waited的值比较高,则说明存在着较严重的表级锁争用情况。这时,需要我们对应用做进一步的检查,来确定问题所在。


三、InnoDB的锁模型
InnoDB是行锁而MyISAM是表锁,对于高并发写入的应用InnoDB效果会好些。
InnoDB的行级锁有两种类型:
(1)共享锁(shared lock,S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
(2)排它锁(exclusive lock,X):允许获得排它锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。
此外,InnoDB支持多粒度加锁(multiple granularity locking),从而允许对记录和表同时加锁。为此,InnoDB引入意向锁(intention locks),意向锁是针对表的:
(1)意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
(2)意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。

MariaDB
show status like 'Innodb%lock%';
+-------------------------------+-------+
| Variable_name | Value |
+-------------------------------+-------+
| Innodb_deadlocks | 0 |
| Innodb_row_lock_current_waits | 0 |
| Innodb_current_row_locks | 0 |
| Innodb_row_lock_time | 0 |
| Innodb_row_lock_time_avg | 0 |
| Innodb_row_lock_time_max | 0 |
| Innodb_row_lock_waits | 0 |
| Innodb_s_lock_os_waits | 2 |
| Innodb_s_lock_spin_rounds | 60 |
| Innodb_s_lock_spin_waits | 2 |
| Innodb_x_lock_os_waits | 0 |
| Innodb_x_lock_spin_rounds | 0 |
| Innodb_x_lock_spin_waits | 0 |
+-------------------------------+-------+
13 rows in set (0.00 sec)
线上5.1版
mysql> show status like 'Innodb%lock%';
+-------------------------------+----------+
| Variable_name | Value |
+-------------------------------+----------+
| Innodb_row_lock_current_waits | 0 |
| Innodb_row_lock_time | 15086472 |
| Innodb_row_lock_time_avg | 493 |
| Innodb_row_lock_time_max | 51941 |
| Innodb_row_lock_waits | 30562 |
+--------------------------

四、InnoDB什么时候使用表锁
http://hi.baidu.com/bubu600/item/1528fe50b0810edcd48bac1d
http://blog.csdn.net/xiao7ng/article/details/5034013
对于InnoDB表,在绝大部分情况下都应该使用行级锁,因为事务和行锁往往是我们之所以选择InnoDB表的理由。但在个别特殊事务中,也可以考虑使用表级锁。
第一种情况是:事务需要更新大部分或全部数据,表又比较大,如果使用默认的行锁,不仅这个事务执行效率低,而且可能造成其他事务长时间锁等待和锁冲突,这种情况下可以考虑使用表
锁来提高该事务的执行速度。
第二种情况是:事务涉及多个表,比较复杂,很可能引起死锁,造成大量事务回滚。这种情况也可以考虑一次性锁定事务涉及的表,从而避免死锁、减少数据库因事务回滚带来的开销。
当然,应用中这两种事务不能太多,否则,就应该考虑使用MyISAM表了。
在InnoDB下,使用表锁要注意以下两点。
(1)使用LOCK TABLES虽然可以给InnoDB加表级锁,但必须说明的是,表锁不是由InnoDB存储引擎层管理的,而是由其上一层──MySQL Server负责的,
仅当autocommit=0、innodb_table_locks=1(默认设置)时,InnoDB层才能知道MySQL加的表锁,MySQL Server也才能感知InnoDB加的行锁,这种情况下,InnoDB才能自动识别涉及表级锁的死
锁;否则,InnoDB将无法自动检测并处理这种死锁
(2)在用 LOCK TABLES对InnoDB表加锁时要注意,要将AUTOCOMMIT设为0,否则MySQL不会给表加锁;事务结束前,不要用UNLOCK TABLES释放表锁,因为UNLOCK TABLES会隐含地提交事务;
COMMIT或ROLLBACK并不能释放用LOCK TABLES加的表级锁,必须用UNLOCK TABLES释放表锁。正确的方式见如下语句:
例如,如果需要写表t1并从表t读,可以按如下做:
SET AUTOCOMMIT=0;
LOCK TABLES t1 WRITE, t2 READ, ...;
[do something with tables t1 and t2 here];
COMMIT;
UNLOCK TABLES;


五、死锁
MyISAM表锁是deadlock free的,这是因为MyISAM总是一次获得所需的全部锁,要么全部满足,要么等待,因此不会出现死锁。但在InnoDB中,除单个SQL组成的事务外,锁是逐步获得的,这
就决定了在InnoDB中发生死锁是可能的。
发生死锁后,InnoDB一般都能自动检测到,并使一个事务释放锁并回退,另一个事务获得锁,继续完成事务。但在涉及外部锁,或涉及表锁的情况下,InnoDB并不能完全自动检测到死锁,这
需要通过设置锁等待超时参数 innodb_lock_wait_timeout来解决。需要说明的是,这个参数并不是只用来解决死锁问题,在并发访问比较高的情况下,如果大量事务因无法立即获得所需的锁
而挂起,会占用大量计算机资源,造成严重性能问题,甚至拖跨数据库。我们通过设置合适的锁等待超时阈值,可以避免这种情况发生。
通常来说,死锁都是应用设计的问题,通过调整业务流程、数据库对象设计、事务大小,以及访问数据库的SQL语句,绝大部分死锁都可以避免。下面就通过实例来介绍几种避免死锁的常用方
法。
(1)在应用中,如果不同的程序会并发存取多个表,应尽量约定以相同的顺序来访问表,这样可以大大降低产生死锁的机会。在下面的例子中,由于两个session访问两个表的顺序不同,发
生死锁的机会就非常高!但如果以相同的顺序来访问,死锁就可以避免。
(2)在程序以批量方式处理数据的时候,如果事先对数据排序,保证每个线程按固定的顺序来处理记录,也可以大大降低出现死锁的可能。
(3)在事务中,如果要更新记录,应该直接申请足够级别的锁,即排他锁,而不应先申请共享锁,更新时再申请排他锁,因为当用户申请排他锁时,其他事务可能又已经获得了相同记录的共
享锁,从而造成锁冲突,甚至死锁。
尽管通过上面介绍的设计和SQL优化等措施,可以大大减少死锁,但死锁很难完全避免。因此,在程序设计中总是捕获并处理死锁异常是一个很好的编程习惯。
如果出现死锁,可以用SHOW INNODB STATUS命令来确定最后一个死锁产生的原因。返回结果中包括死锁相关事务的详细信息,如引发死锁的SQL语句,事务已经获得的锁,正在等待什么锁,以
及被回滚的事务等。据此可以分析死锁产生的原因和改进措施。
这里主要摘自<深入浅出MySQL——数据库开发、优化与管理维护>这本书里面的20.3 InnoDB锁问题

使用Show innodb status检查引擎状态时,发现了死锁问题或者查看slow-log

解决死锁问题案例
故障实例
修改存储引擎-MyISAM修改为InnoDB:http://hi.baidu.com/dmkj2008/item/ab239196528fa8b9cc80e554
语句改进解决 InnoDB引擎情况
http://blog.csdn.net/zhangzhao100/article/details/7981755

六、锁的操作
LOCK TABLES
tbl_name [AS alias] {READ [LOCAL] | [LOW_PRIORITY] WRITE}
[, tbl_name [AS alias] {READ [LOCAL] | [LOW_PRIORITY] WRITE}] ...
UNLOCK TABLES
单表只读锁
mysql> lock tables tbl_ftpuser read;
mysql> unlock tables;

全局只读锁
mysql> FLUSH TABLES WITH READ LOCK;
mysql> unlock tables;

七、优化
(一)MyISAM 表锁优化建议
在优化MyISAM存储引擎锁定问题的时候,最关键的就是如何让其提高并发度
1、缩短锁定时间
尽两减少大的复杂Query,将复杂Query分拆成几个小的Query分布进行;
尽可能的建立足够高效的索引,让数据检索更迅速;
尽量让MyISAM存储引擎的表只存放必要的信息,控制字段类型;
利用合适的机会优化MyISAM表数据文件;
2、分离能并行的操作
MyISAM存储引擎有一个控制是否打开Concurrent Insert功能的参数选项:concurrent_insert,可以设置为0,1或者2。三个值的具体说明如下:
concurrent_insert=2,无论MyISAM存储引擎的表数据文件的中间部分是否存在因为删除数据而留下的空闲空间,都允许在数据文件尾部进行ConcurrentInsert;
concurrent_insert=1,当MyISAM存储引擎表数据文件中间不存在空闲空间的时候,可以从文件尾部进行ConcurrentInsert;
concurrent_insert=0,无论MyISAM存储引擎的表数据文件的中间部分是否存在因为删除数据而留下的空闲空间,都不允许ConcurrentInsert。
3、合理利用读写优先级
在本章各种锁定分析一节中我们了解到了MySQL的表级锁定对于读和写是有不同优先级设定的,默认情况下是写优先级要大于读优先级。所以,如果我们可以根据各自系统环境的差异决定读与
写的优先级。如果我们的系统是一个以读为主,而且要优先保证查询性能的话,我们可以通过设置系统参数选项low_priority_updates=1,将写的优先级设置为比读的优先级低,即可让告诉
MySQL尽量先处理读请求。当然,如果我们的系统需要有限保证数据写入的性能的话,则可以不用设置low_priority_updates参数了。
这里我们完全可以利用这个特性,将concurrent_insert参数设置为1,甚至如果数据被删除的可能性很小的时候,如果对暂时性的浪费少量空间并不是特别的在乎的话,将concurrent_insert
参数设置为2都可以尝试。当然,数据文件中间留有空域空间,在浪费空间的时候,还会造成在查询的时候需要读取更多的数据,所以如果删除量不是很小的话,还是建议将
concurrent_insert设置为1更为合适


(二)Innodb 行锁优化建议:
尽可能让所有的数据检索都通过索引来完成,从而避免Innodb因为无法通过索引键加锁而升级为表级锁定;
合理设计索引,让Innodb在索引键上面加锁的时候尽可能准确,尽可能的缩小锁定范围,避免造成不必要的锁定而影响其他Query的执行;
尽可能减少基于范围的数据检索过滤条件,避免因为间隙锁带来的负面影响而锁定了不该锁定的记录;
尽量控制事务的大小,减少锁定的资源量和锁定时间长度;
在业务环境允许的情况下,尽量使用较低级别的事务隔离,以减少MySQL因为实现事务隔离级别所带来的附加成本;

posted on 2014-05-06 23:36  @Jin  阅读(493)  评论(0编辑  收藏  举报

导航