悲观锁与乐观锁:mysql
悲观锁:顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,因此每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了不少这种锁机制,好比行锁,表锁等,读锁,写锁等,都是在作操做以前先上锁。react
乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,因此不会上锁,可是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可使用版本号等机制。乐观锁适用于多读的应用类型,这样能够提升吞吐量,像数据库若是提供相似于write_condition机制的其实都是提供的乐观锁。sql
表级:引擎 MyISAM,直接锁定整张表,在你锁按期间,其它进程没法对该表进行写操做。若是你是写锁,则其它进程则读也不容许数据库
页级:引擎 BDB,表级锁速度快,但冲突多,行级冲突少,但速度慢。因此取了折衷的页级,一次锁定相邻的一组记录缓存
行级:引擎 INNODB, 仅对指定的记录进行加锁,这样其它进程仍是能够对同一个表中的其它记录进行操做。性能优化
上述三种锁的特性可大体概括以下:session
1) 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的几率最高,并发度最低。并发
2) 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度通常。oracle
3) 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的几率最低,并发度也最高。性能
三种锁各有各的特色,若仅从锁的角度来讲,表级锁更适合于以查询为主,只有少许按索引条件更新数据的应用,如WEB应用;行级锁更适合于有大量按索引条件并发更新少许不一样数据,同时又有并发查询的应用,如一些在线事务处理(OLTP)系统。
MySQL表级锁有两种模式:
一、表共享读锁(Table Read Lock)。对MyISAM表进行读操做时,它不会阻塞其余用户对同一表的读请求,但会阻塞 对同一表的写操做;
二、表独占写锁(Table Write Lock)。对MyISAM表的写操做,则会阻塞其余用户对同一表的读和写操做。
MyISAM表的读和写是串行的,即在进行读操做时不能进行写操做,反之也是同样。但在必定条件下MyISAM表也支持查询和插入的操做的并发进行,其机制是经过控制一个系统变量(concurrent_insert)来进行的,当其值设置为0时,不容许并发插入;当其值设置为1时,若是MyISAM表中没有空洞(即表中没有被删除的行),MyISAM容许在一个进程读表的同时,另外一个进程从表尾插入记录;当其值设置为2时,不管MyISAM表中有没有空洞,都容许在表尾并发插入记录。
MyISAM锁调度是如何实现的呢,这也是一个很关键的问题。例如,当一个进程请求某个MyISAM表的读锁,同时另外一个进程也请求同一表的写锁,此时mysql将会如优先处理进程呢?经过研究代表,写进程将先得到锁(即便读请求先到锁等待队列)。但这也形成一个很大的缺陷,即大量的写操做会形成查询操做很难得到读锁,从而可能形成永远阻塞。所幸咱们能够经过一些设置来调节MyISAM的调度行为。咱们可经过指定参数low-priority-updates,使MyISAM默认引擎给予读请求以优先的权利,设置其值为1(set low_priority_updates=1),使优先级下降。
InnoDB锁与MyISAM锁的最大不一样在于:
一、是支持事务(TRANCSACTION)。
二、是采用了行级锁。
咱们知道事务是由一组SQL语句组成的逻辑处理单元,其有四个属性(简称ACID属性),分别为:
原子性(Atomicity):事务是一个原子操做单元,其对数据的修改,要么所有执行,要么全都不执行;
一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态;
隔离性(Isolation):数据库系统提供必定的隔离机制,保证事务在不受外部并发操做影响的“独立”环境执行;
持久性(Durable):事务完成以后,它对于数据的修改是永久性的,即便出现系统故障也可以保持。
并发事务处理带来的问题
相对于串行处理来讲,并发事务处理能大大增长数据库资源的利用率,提升数据库系统的事务吞吐量,从而能够支持更多的用户。但并发事务处理也会带来一些问题,主要包括如下几种状况。
一、更新丢失(Lost Update):当两个或多个事务选择同一行,而后基于最初选定的值更新该行时,因为每一个事务都不知道其余事务的存在,就会发生丢失更新问题--最后的更新覆盖了由其余事务所作的更新。例如,两个编辑人员制做了同一文档的电子副本。每一个编辑人员独立地更改其副本,而后保存更改后的副本,这样就覆盖了原始文档。最后保存其更改副本的编辑人员覆盖另外一个编辑人员所作的更改。若是在一个编辑人员完成并提交事务以前,另外一个编辑人员不能访问同一文件,则可避免此问题。
二、脏读(Dirty Reads):一个事务正在对一条记录作修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另外一个事务也来读取同一条记录,若是不加控制,第二个事务读取了这些“脏”数据,并据此作进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫作”脏读”。
三、不可重复读(Non-Repeatable Reads):一个事务在读取某些数据后的某个时间,再次读取之前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫作“不可重复读”。
四、幻读(Phantom Reads):一个事务按相同的查询条件从新读取之前检索过的数据,却发现其余事务插入了知足其查询条件的新数据,这种现象就称为“幻读”。
事务隔离级别
在上面讲到的并发事务处理带来的问题中,“更新丢失”一般是应该彻底避免的。但防止更新丢失,并不能单靠数据库事务控制器来解决,须要应用程序对要更新的数据加必要的锁来解决,所以,防止更新丢失应该是应用的责任。
“脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供必定的事务隔离机制来解决。数据库实现事务隔离的方式,基本上可分为如下两种。
一、一种是在读取数据前,对其加锁,阻止其余事务对数据进行修改。
二、另外一种是不用加任何锁,经过必定机制生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供必定级别(语句级或事务级)的一致性读取。从用户的角度来看,好像是数据库能够提供同一数据的多个版本,所以,这种技术叫作数据多版本并发控制(MultiVersion Concurrency Control,简称MVCC或MCC),也常常称为多版本数据库。
数据库的事务隔离越严格,并发反作用越小,但付出的代价也就越大,由于事务隔离实质上就是使事务在必定程度上 “串行化”进行,这显然与“并发”是矛盾的。同时,不一样的应用对读一致性和事务隔离程度的要求也是不一样的,好比许多应用对“不可重复读”和“幻读”并不敏感,可能更关心数据并发访问的能力。
为了解决“隔离”与“并发”的矛盾,ISO/ANSI SQL92定义了4个事务隔离级别,每一个级别的隔离程度不一样,容许出现的反作用也不一样,应用能够根据本身的业务逻辑要求,经过选择不一样的隔离级别来平衡 “隔离”与“并发”的矛盾。表20-5很好地归纳了这4个隔离级别的特性。
读数据一致性及容许的并发反作用
隔离级别 读数据一致性 脏读 不可重复读 幻读
未提交读(Read uncommitted) 最低级别,只能保证不读取物理上损坏的数据 是 是 是
已提交度(Read committed) 语句级 否 是 是
可重复读(Repeatable read) 事务级 否 否 是
可序列化(Serializable) 最高级别,事务级 否 否 否
最后要说明的是:各具体数据库并不必定彻底实现了上述4个隔离级别,例如,Oracle只提供Read committed和Serializable两个标准隔离级别,另外还提供本身定义的Read only隔离级别;SQL Server除支持上述ISO/ANSI SQL92定义的4个隔离级别外,还支持一个叫作“快照”的隔离级别,但严格来讲它是一个用MVCC实现的Serializable隔离级别。MySQL支持所有4个隔离级别,但在具体实现时,有一些特色,好比在一些隔离级别下是采用MVCC一致性读,但某些状况下又不是
InnoDB有两种模式的行锁:
1)共享锁(S):容许一个事务去读一行,阻止其余事务得到相同数据集的排他锁。
( Select * from table_name where ……lock in share mode)
2)排他锁(X):容许得到排他锁的事务更新数据,阻止其余事务取得相同数据集的共享读锁和排他写锁。(select * from table_name where…..for update)
为了容许行锁和表锁共存,实现多粒度锁机制;同时还有两种内部使用的意向锁(都是表锁),分别为意向共享锁和意向排他锁。
1)意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
2)意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。
InnoDB行锁模式兼容性列表
请求锁模式
是否兼容
当前锁模式 X IX S IS
X 冲突 冲突 冲突 冲突
IX 冲突 兼容 冲突 兼容
S 冲突 冲突 兼容 兼容
IS 冲突 兼容 兼容 兼容
若是一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;反之,若是二者不兼容,该事务就要等待锁释放。
意向锁是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行锁是经过给索引上的索引项加锁来实现的,这一点MySQL与oracle不一样,后者是经过在数据块中对相应数据行加锁来实现的。InnoDB这种行锁实现特色意味着:只有经过索引条件检索数据,InnoDB才使用行级锁,不然,InnoDB将使用表锁!
在实际应用中,要特别注意InnoDB行锁的这一特性,否则的话,可能致使大量的锁冲突,从而影响并发性能。
查询表级锁争用状况
表锁定争夺:
能够经过检查table_locks_waited和table_locks_immediate状态变量来分析系统上的表锁定争夺:
mysql> show status like ‘table%’;
+———————–+——-+
| Variable_name | Value |
+———————–+——-+
| Table_locks_immediate | 2979 |
| Table_locks_waited | 0 |
+———————–+——-+
2 rowsin set(0.00 sec))
若是Table_locks_waited的值比较高,则说明存在着较严重的表级锁争用状况。
InnoDB行锁争夺:
能够经过检查InnoDB_row_lock状态变量来分析系统上的行锁的争夺状况:
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 rowsin set(0.01 sec)
MyISAM写锁实验:
对MyISAM表的读操做,不会阻塞其余用户对同一表的读请求,但会阻塞对同一表的写请求;对MyISAM表的写操做,则会阻塞其余用户对同一表的读和写操做;MyISAM表的读操做与写操做之间,以及写操做之间是串行的!根据如表20-2所示的例子能够知道,当一个线程得到对一个表的写锁后,只有持有锁的线程能够对表进行更新操做。其余线程的读、写操做都会等待,直到锁被释放为止。
USER1:
mysql> lock tablefilm_text write;
当前session对锁定表的查询、更新、插入操做均可以执行:
mysql> selectfilm_id,title fromfilm_text wherefilm_id = 1001;
USER2:
mysql> selectfilm_id,title fromfilm_text wherefilm_id = 1001;
等待
USER1:
释放锁:
mysql> unlock tables;
USER2:
得到锁,查询返回:
InnoDB存储引擎的共享锁实验
USER1:
mysql> setautocommit = 0;
USER2:
mysql> setautocommit = 0;
USER1:
当前session对actor_id=178的记录加share mode 的共享锁:
mysql> selectactor_id,first_name,last_name fromactor whereactor_id = 178lock in share mode;
USER2:
其余session仍然能够查询记录,并也能够对该记录加share mode的共享锁:
mysql> selectactor_id,first_name,last_name fromactor whereactor_id = 178lock in share mode;
USER1:
当前session对锁定的记录进行更新操做,等待锁:
mysql> updateactor setlast_name = ‘MONROE T’ whereactor_id = 178;
等待
USER2:
其余session也对该记录进行更新操做,则会致使死锁退出:
mysql> updateactor setlast_name = ‘MONROE T’ whereactor_id = 178;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
USER1:
得到锁后,能够成功更新:
mysql> updateactor setlast_name = ‘MONROE T’ whereactor_id = 178;
Query OK, 1 row affected (17.67 sec)
Rowsmatched: 1 Changed: 1 Warnings: 0
InnoDB存储引擎的排他锁例子
USER1:
mysql> setautocommit = 0;
USER2:
mysql> setautocommit = 0;
USER1:
当前session对actor_id=178的记录加for update的排它锁:
mysql> selectactor_id,first_name,last_name fromactor whereactor_id = 178 forupdate;
USER2:
其余session能够查询该记录,可是不能对该记录加共享锁,会等待得到锁:
mysql> selectactor_id,first_name,last_name fromactor whereactor_id = 178;
USER1:
当前session能够对锁定的记录进行更新操做,更新后释放锁:
mysql> updateactor setlast_name = ‘MONROE T’ whereactor_id = 178;
USER2:
其余session得到锁,获得其余session提交的记录:
mysql> selectactor_id,first_name,last_name fromactor whereactor_id = 178 forupdate;
更新性能优化的几个重要参数
bulk_insert_buffer_size
批量插入缓存大小,这个参数是针对MyISAM存储引擎来讲的.适用于在一次性插入100-1000+条记录时,提升效率.默认值是8M.能够针对数据量的大小,翻倍增长.
concurrent_insert
并发插入,当表没有空洞(删除过记录),在某进程获取读锁的状况下,其余进程能够在表尾部进行插入.
值能够设0不容许并发插入, 1当表没有空洞时,执行并发插入, 2无论是否有空洞都执行并发插入.
默认是1针对表的删除频率来设置.
delay_key_write
针对MyISAM存储引擎,延迟更新索引.意思是说,update记录时,先将数据up到磁盘,但不up索引,将索引存在内存里,当表关闭时,将内存索引,写到磁盘.值为 0不开启, 1开启.默认开启.
delayed_insert_limit, delayed_insert_timeout, delayed_queue_size
延迟插入,将数据先交给内存队列,而后慢慢地插入.可是这些配置,不是全部的存储引擎都支持,目前来看,经常使用的InnoDB不支持, MyISAM支持.根据实际状况调大,通常默认够用了。