mysql之表锁与行锁

来源:互联网 发布:程序员老黄历开车 编辑:程序博客网 时间:2024/05/21 09:01

整理自
http://blog.csdn.net/yunlong34574/article/details/47341467
的笔记

MySQL这3种锁的特性可大致归纳如下。
·表级锁(适合查询为主):开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
·行级锁(适合并发):开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用
·页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。

MyISAM和MEMORY存储引擎:表级锁(table-level locking)
BDB存储引擎:采用的是页面锁(page-level locking),但也支持表级锁
InnoDB存储引擎:既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁

下面重点介绍MyISAM表锁和InnoDB行锁的问题,由于BDB页锁已经被InnoDB取代,即将成为历史,就不做进一步的讨论。

表锁:

查询表级锁争用情况:
可以通过检查table_locks_waited和table_locks_immediate状态变量来分析系统上的表锁定争夺:
mysql> show status like ‘table%’;
+———————–+——-+
| Variable_name | Value |
+———————–+——-+
| Table_locks_immediate | 2979 |
| Table_locks_waited | 0 |
+———————–+——-+
2 rows in set (0.00 sec))
如果Table_locks_waited的值比较高,则说明存在着较严重的表级锁争用情况

表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)

对写锁,MySQL使用的表锁定方法原理如下:
如果在表上没有锁,在它上面放一个写锁。
否则,把锁定请求放在写锁定队列中。
对读锁,MySQL使用的表锁定方法原理如下:
如果在表上没有写锁定,在它上面放一个读锁。
否则,把锁请求放在读锁定队列中。
综上:写锁优先获得。(即使读请求先到锁等待队列,写请求后到,写锁也会插到读锁请求之前,这也正是MyISAM表不太适合于有大量更新操作和查询操作应用的原因,因为,大量的更新操作会造成查询操作很难获得读锁,从而可能永远阻塞。)

这里写图片描述

如何加表锁
MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此,用户一般不需要直接用LOCK TABLE命令给MyISAM表显式加锁。
在用LOCK TABLES给表显式加表锁时,必须同时取得所有涉及到表的锁,并且MySQL不支持锁升级。也就是说,在执行LOCK TABLES后,只能访问显式加锁的这些表,不能访问未加锁的表;同时,如果加的是读锁,那么只能执行查询操作,而不能执行更新操作。其实,在自动加锁的情况下也基本如此,MyISAM总是一次获得SQL语句所需要的全部锁。这也正是MyISAM表不会出现死锁(Deadlock Free)的原因

当使用LOCK TABLES时,不仅需要一次锁定用到的所有表,而且,同一个表如果使用别名在SQL语句中出现,就要重新为该别名额外上锁,否则也会出错!举例说明如下。
1、对actor表获得读锁:mysql> lock table actor read;
2、通过别名访问会提示错误:
mysql> select a.first_name,a.last_name from actor a ;
ERROR 1100 (HY000): Table ‘a’ was not locked with LOCK TABLES
3、需要对别名分别锁定:mysql> lock table actor as a read;
4、此时可以正确执行:select a.first_name,a.last_name from actor a ;

并发插入(Concurrent Inserts)
MyISAM表的读和写是串行的,但这是就总体而言的。在一定条件下,MyISAM表也支持查询和插入操作的并发进行。
MyISAM存储引擎有一个系统变量concurrent_insert,专门用以控制其并发插入的行为,其值分别可以为0、1或2。
·当concurrent_insert设置为0时,不允许并发插入
·当concurrent_insert设置为1时,如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录。这也是MySQL的默认设置
·当concurrent_insert设置为2时,无论MyISAM表中有没有空洞,都允许在表尾并发插入记录。

session_1获得了一个表的READ LOCAL锁(“LOCAL”作用就是在满足MyISAM表并发插入条件的情况下,允许其他用户在表尾并发插入记录),该线程可以对表进行查询操作,但不能对表进行删除、插入、更新操作;其他的线程(session_2),虽然不能对表进行删除和更新操作,但却可以对该表进行并发插入操作(更新操作会被等待),这里假设该表中间不存在空洞。而session_2插入一条记录后,session_1此时是访问不到该记录的,搜索出来为空。

调节MyISAM的调度行为:
·通过指定启动参数low-priority-updates,使MyISAM引擎默认给予读请求以优先的权利。
·通过执行命令SET LOW_PRIORITY_UPDATES=1,使该连接发出的更新请求优先级降低。
·通过指定INSERT、UPDATE、DELETE语句的LOW_PRIORITY属性,降低该语句的优先级。
虽然上面3种方法都是要么更新优先,要么查询优先的方法,但还是可以用其来解决查询相对重要的应用(如用户登录系统)中,读锁等待严重的问题。另外,MySQL也提供了一种折中的办法来调节读写冲突,即给系统参数max_write_lock_count设置一个合适的值,当一个表的读锁达到这个值后,MySQL就暂时将写请求的优先级降低,给读进程一定获得锁的机会。

行锁:

InnoDB行锁争用情况 :
mysql> show status like ‘innodb_row_lock%’;
+——————————-+——-+
| Variable_name | Value |
+——————————-+——-+
| InnoDB_row_lock_current_waits | 0 |
| InnoDB_row_lock_time | 0 |
| InnoDB_row_lock_time_avg | 0 |
| InnoDB_row_lock_time_max | 0 |
| InnoDB_row_lock_waits | 0 |
+——————————-+——-+
5 rows in set (0.01 sec)
如果发现锁争用比较严重,如InnoDB_row_lock_waits和InnoDB_row_lock_time_avg的值比较高,还可以通过设置InnoDB Monitors来进一步观察发生锁冲突的表、数据行等,并分析锁争用的原因。
mysql> CREATE TABLE innodb_monitor(a INT) ENGINE=INNODB;
用下面的语句来进行查看:
mysql> Show innodb status/G;
发出下列语句来停止查看:
DROP TABLE innodb_monitor;
设置监视器后,在SHOW INNODB STATUS的显示内容中,会有详细的当前锁等待的信息,包括表名、锁类型、锁定记录的情况等,便于进行进一步的分析和问题的确定。打开监视器以后,默认情况下每15秒会向日志中记录监控的内容,如果长时间打开会导致.err文件变得非常的巨大,所以用户在确认问题原因之后,要记得删除监控表以关闭监视器,或者通过使用“–console”选项来启动服务器以关闭写日志文件。

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

用SELECT … IN SHARE MODE获得共享锁,主要用在需要数据依存关系时来确认某行记录是否存在,并确保没有人对这个记录进行UPDATE或者DELETE操作。但是如果当前事务也需要对该记录进行更新操作,则很有可能造成死锁,对于锁定行记录后需要进行更新操作的应用,应该使用SELECT… FOR UPDATE方式获得排他锁。

造成死锁的实例:
这里写图片描述

InnoDB行锁实现方式:
InnoDB行锁是通过给索引上的索引项加锁来实现的,InnoDB这种行锁实现特点意味着,只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!(要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能)

(1)在不通过索引条件查询的时候,InnoDB确实使用的是表锁,而不是行锁

这里写图片描述

(2)由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键值,是会出现锁冲突的。应用设计的时候要注意这一点。
假设表tab_with_index只有id字段有索引
这里写图片描述

(3)当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB都会使用行锁来对数据加锁。
假设表tab_with_index有两个索引,主键id与索引name
这里写图片描述
(4)在where条件中使用了索引字段,但是否会触发行锁主要看where条件中是否会走索引。如果where条件中不走索引,比如不等于、null判断等,将导致不走索引,因为也不会触发行锁

1 0
原创粉丝点击