mysql最初是但愿设计出一种独立于各类存储引擎的锁定机制,mysql存储引擎的设计者是创建在“任何表在同一时刻只容许单个线程对其进行访问(包括读)”这样的假设的基础之上的!很明显,如今的mysql并非这个样子的,由于mysql现在已经发展成为了一款多用户、多线程的mysql关系型数据库! 其显著特色就是不一样的存储引擎支持不一样的锁机制!在咱们浅析锁机制以前,须要先明白一些基础的概念!mysql
一次性封锁:在sql语句的开始执行的时候,已经预先知道要涉及到那些数据,而后所有锁住,在执行完毕以后,再所有解锁!(myisam就是采用这样的锁协议)sql
两段锁: 是指每一个事务的执行能够分为两个阶段:生长阶段(加锁阶段)和衰退阶段(解锁阶段)。数据库
加锁阶段:在该阶段能够进行加锁操做。在对任何数据进行读操做以前要申请并得到S锁,在进行写操做以前要申请并得到X锁。加锁不成功,则事务进入等待状态,直到加锁成功才继续执行;服务器
解锁阶段:当事务释放了一个封锁之后,事务进入解锁阶段,在该阶段只能进行解锁操做不能再进行加锁操做。session
两段封锁法能够这样来实现:事务开始后就处于加锁阶段,一直到执行ROLLBACK和COMMIT以前都是加锁阶段。ROLLBACK和COMMIT使事务进入解锁阶段,即在ROLLBACK和COMMIT模块中DBMS释放全部封锁。(innodb就是采起这样的锁协议)多线程
行级别的锁定、页级别的锁定、表级别的锁定!并发
行级锁定:
行级锁定最大的特色就是锁定对象的颗粒度很小,因为锁定颗粒度很小,因此发生锁定资源争用的几率也最小,可以给予应用程序尽量大的并发处理能力而提升一些须要高并发应用系统的总体性能。
虽然可以在并发处理能力上面有较大的优点,可是行级锁定也所以带来了很多弊端。因为锁定资源的颗粒度很小,因此每次获取锁和释放锁须要作的事情也更多,带来的消耗天然也就更大了。此外,行级锁定也最容易发生死锁。高并发
表级锁定:
和行级锁定相反,表级别的锁定是MySQL各存储引擎中最大颗粒度的锁定机制。该锁定机制最大的特色是实现逻辑很是简单,带来的系统负面影响最小。因此获取锁和释放锁的速度很快。固然,锁定颗粒度大所带来最大的负面影响就是出现锁定资源争用的几率也会最高,导致并大度大打折扣。此外,表级别的锁定是不会产生死锁问题的。性能
页级锁定:
页级锁定是MySQL中比较独特的一种锁定级别,在其余数据库管理软件中也并非太常见。页级锁定的特色是锁定颗粒度介于行级锁定与表级锁之间,因此获取锁定所须要的资源开销,以及所能提供的并发处理能力也一样是介于上面两者之间。固然,也会产生死锁问题。spa
行级锁定:innodb(innodb的锁是创建在索引基础上的,必要的时候会由行锁升级为表锁,因此,innodb既支持表锁也支持行锁)
表级锁定:myisam、memory,innodb,BDB
页级锁定: BDB存储引擎采用的是页面锁(page-level locking),但也支持表级锁
mysql锁分为隐式锁和显式锁。当多个客户端并发访问同一个数据的时候,为了保证数据的一致性,数据库管理系统会自动的为该数据加锁、解锁,这种被称为隐式锁。隐式锁无需开发人员维护(包括锁粒度、加锁时机、解锁时机等)
当时在某些特殊的状况下须要开发人员手动的进行加锁、解锁,这种锁方式被称为显式锁。对于显式锁而言,开发人员不只要肯定锁的粒度,还须要肯定加锁的时机(什么时候加锁)、解锁的时机(什么时候解锁)以及所的类型。
锁的生命周期是指在一个msql回话内,对数据进行加锁到解锁之间的时间间隔。锁的声明周期越长,并发性能就越低;锁的声明周期越短,并发性能就越高。另外锁是数据库管理系统的重要资源,须要占据必定的服务器内存,锁的周期越长,占用的服务器内存时间就越长;相反若是锁周期越短,占用的内存也就越短。所以,总的来讲,咱们应该尽量的缩短锁的生命周期。
读锁(read lock,也叫共享锁): 不会阻塞其余用户对锁定数据的读请求,但会阻塞对锁定数据的写请求。
写锁(x lock,也叫排它锁): 会阻塞其余用户对锁定数据的读和写操做。
MyISAM存储引擎只支持表锁,这也是MySQL开始几个版本中惟一支持的锁类型。随着应用对事务完整性和 并发性要求的不断提升,MySQL才开始开发基于事务的存储引擎,后来慢慢出现了支持页锁的BDB存储引擎和支持行锁的InnoDB存储引擎(实际 InnoDB是单独的一个公司,如今已经被Oracle公司收购)。可是MyISAM的表锁依然是使用最为普遍的锁类型。
在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)
当前持有读锁的全部线程的相关信息都可以在Current read-lock queue 中找到,队列中的信息按照获取到锁的时间依序存放。而正在等待锁定资源的信息则存放在Pending read-lock queue 里面,另外两个存放写锁信息的队列也按照上面相同规则来存放信息。
如何加读锁?
请求的资源当前没有被写锁定,就是没有在Current write-lock queue 队列中出现;
写锁定的等待队列中(Pending write-lock queue )队列中并无优先级更高的写锁定等待;
知足上面的两个条件以后,请求会被当即经过,并将相关信息存入到Current read-lock queue队列中,而若是有一个条件没有知足,就会被迫进入到Pending-read-lock-queue队列中进行等待。
如何加写锁?
在Current write-lock queue中是否有锁定相同的资源;
在Pending write-lock queue中是否有锁定相同的资源;
在Current read-lock queue中是否有锁定相同的资源;
知足上面的三个条件后,请求会被当即经过,将相关信息存入到Current write-lock queue队列中,而若是有一个条件没有知足,就会被迫进入到Pending-write-lock-queue队列中进行等待!
隐式加锁?
myisam存储引擎在执行sql语句以前会自动为涉及到的表加锁。
MyISAM在执行查询语句(SELECT)前,会自动给涉及的全部表加读锁,在执行更新操做 (UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不须要用户干预,所以,用户通常不须要直接用LOCK TABLE命令给MyISAM表显式加锁。在示例中,显式加锁基本上都是为了模拟而已。
显式加锁?
在用LOCK TABLES给表显式加表锁时,必须同时取得全部涉及到表的锁,而且MySQL不支持锁升级。也就是说,在执行LOCK TABLES后,只能访问显式加锁的这些表,不能访问未加锁的表;同时,若是加的是读锁,那么只能执行查询操做,而不能执行更新操做。其实,在自动加锁的 状况下也基本如此,MyISAM老是一次得到SQL语句所须要的所有锁。这也正是MyISAM表不会出现死锁(Deadlock Free)的缘由。
lock table 表名 【redad|write】;加锁
unlock tables; 解锁
上文提到过MyISAM表的读和写是串行的,但这是就整体而言的。在必定条件下,MyISAM表也支持查询和插入操做的并发进行。
MyISAM存储引擎有一个系统变量concurrent_insert,专门用以控制其并发插入的行为,其值分别能够为0、1或2。
当concurrent_insert设置为0时,不容许并发插入。 当concurrent_insert设置为1时,若是MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM容许在一个进程读表的同时, 另外一个进程从表尾插入记录。这也是MySQL的默认设置。 当concurrent_insert设置为2时,不管MyISAM表中有没有空洞,都容许在表尾并发插入记录
前面讲过,MyISAM存储引擎的读锁和写锁是互斥的,读写操做是串行的。那么,一个进程请求某个 MyISAM表的读锁,同时另外一个进程也请求同一表的写锁,MySQL如何处理呢?答案是写进程先得到锁。
不只如此,即便读请求先到锁等待队列,写请求后到,写锁也会插到读锁请求以前!这是由于MySQL认为写请求通常比读请求要重要。这也正是MyISAM表不太适合于有大量更新操做和查询操做应用的缘由,由于,大量的更新操做会形成查询操做很难得到读锁,从而可能永远阻塞。这种状况有时可能会变得很是糟糕!幸亏咱们能够经过一些设置来调MyISAM 的调度行为。
经过指定启动参数low-priority-updates,使全部的更新操做优先级比select语句的优先级低。
向特定的insert、delete、update语句添加 low_priority选项,下降这些操做的优先级。 这种调度修改暗示着,可能存在LOW_PRIORITY写入操做永远被阻塞的状况。若是前面的读取操做在进行的过程当中一直有其它的读取操做到达,那么新的请求都会插入到LOW_PRIORITY写入操做以前。
向特定的select语句添加high_priority选项,提升检索操做的优先级。 它容许SELECT插入正在等待的写入操做以前,即便在正常状况下写入操做的优先级更高。另一种影响是,高优先级的SELECT在正常的SELECT语句以前执行,由于这些语句会被写入操做阻塞。
虽然上面3种方法都是要么更新优先,要么查询优先的方法,但仍是能够用其来解决查询相对重要的应用(如用户登陆系统)中,读锁等待严重的问题。(可是我的建议不要使用,除非你真的肯定要这么干)。
另外,MySQL也提供了一种折中的办法来调节读写冲突,即给系统参数max_write_lock_count设置一个合适的值,当一个表的读锁达到这个值后,MySQL就暂时将写请求的优先级下降,给读进程必定得到锁的机会。上面已经讨论了写优先调度机制带来的问题和解决办法。这 里还要强调一点:一些须要长时间运行的查询操做,也会使写进程“饿死”!所以,应用中应尽可能避免出现长时间运行的查询操做,不要总想用一条SELECT语 句来解决问题,由于这种看似巧妙的SQL语句,每每比较复杂,执行时间较长,在可能的状况下能够经过使用中间表等措施对SQL语句作必定的“分解”,使每 一步查询都能在较短期完成,从而减小锁冲突。若是复杂查询不可避免,应尽可能安排在数据库空闲时段执行,好比一些按期统计能够安排在夜间执行。
这里有两个状态变量记录MySQL 内部表级锁定的状况,两个变量说明以下:
Table_locks_immediate:使用表级锁后当即释放表级锁的次数。
Table_locks_wait:出现表级锁争用而发生等待的次数;
两个状态值都是从系统开启后开始记录的,每出现一次对应的事件数量就会加1。
create table film_text
(
film_id int not null auto_increment, title varchar(64) not null default '', primary key (film_id)
)engine=myisam charset=utf8;
insert into film_text (film_id, title) values ('1001', 'Update Test');
=================================================================================================
create table film
(
film_id int not null auto_increment, title varchar(64) not null default '', primary key (film_id)
)engine=myisam charset=utf8;
insert into film (film_id, title) values ('1001', 'Update Test');
session1 | session2 |
首先获取film_text表的write锁定: ![]() |
|
在当前会话中对锁定表的查询、更新、插入操做都是能够的: ![]() ![]() |
在其余的会话中对锁定表的任何操做都会被阻塞(串行化): ![]() |
在当前会话中,释放对表的锁定: ![]() |
执行获得结果: ![]() |
对film_text表加读锁 ![]() |
|
在当前会话中,只能对锁定到的表进行查询操做,成功的对锁定的表进行了查询: ![]() ![]() ![]() |
在其余会话中,能够对锁定的表进行查询操做 ![]() ![]() |
释放对该表的锁定: ![]() |
更新操做完成: ![]() ![]() |
对film_text表加上read local锁定: ![]() |
|
当前会话不可以对锁定表进行更新、插入、删除的操做,可是查询操做是能够的 : ![]() |
其余的会话,能够对进行查询、和插入操做: ![]() ![]() ![]() |
释放锁: ![]() |
执行了删除操做: ![]() |
ps:READ LOCAL和READ之间的区别是,READ LOCAL容许在锁定被保持时,执行非冲突性INSERT语句(同时插入)。 对于InnoDB表,READ LOCAL与READ相同。
以上是对我的对mysql锁机制的一些理解,若是有理解错误或者不到位的地方,但愿高手不吝赐教!