悲观锁与乐观锁:
悲观锁:顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
表级:引擎 myisam,直接锁定整张表,在你锁定期间,其它进程无法对该表进行写操作。如果你是写锁,则其它进程则读也不允许
页级:引擎 bdb,表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录
行级:引擎 innodb, 仅对指定的记录进行加锁,这样其它进程还是可以对同一个表中的其它记录进行操作。
上述三种锁的特性可大致归纳如下:
1) 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
2) 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
3) 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
三种锁各有各的特点,若仅从锁的角度来说,表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如web应用;行级锁更适合于有大量按索引条件并发更新少量不同数据,同时又有并发查询的应用,如一些在线事务处理(oltp)系统。
mysql表级锁有两种模式:
1、表共享读锁(table read lock)。对myisam表进行读操作时,它不会阻塞其他用户对同一表的读请求,但会阻塞 对同一表的写操作;
2、表独占写锁(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锁的最大不同在于:
1、是支持事务(trancsaction)。
2、是采用了行级锁。
我们知道事务是由一组sql语句组成的逻辑处理单元,其有四个属性(简称acid属性),分别为:
原子性(atomicity):事务是一个原子操作单元,其对数据的修改,要么全部执行,要么全都不执行;
一致性(consistent):在事务开始和完成时,数据都必须保持一致状态;
隔离性(isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行;
持久性(durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。
并发事务处理带来的问题
相对于串行处理来说,并发事务处理能大大增加数据库资源的利用率,提高数据库系统的事务吞吐量,从而可以支持更多的用户。但并发事务处理也会带来一些问题,主要包括以下几种情况。
1、更新丢失(lost update):当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题--最后的更新覆盖了由其他事务所做的更新。例如,两个编辑人员制作了同一文档的电子副本。每个编辑人员独立地更改其副本,然后保存更改后的副本,这样就覆盖了原始文档。最后保存其更改副本的编辑人员覆盖另一个编辑人员所做的更改。如果在一个编辑人员完成并提交事务之前,另一个编辑人员不能访问同一文件,则可避免此问题。
2、脏读(dirty reads):一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做”脏读”。
3、不可重复读(non-repeatable reads):一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读”。
4、幻读(phantom reads):一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。
事务隔离级别
在上面讲到的并发事务处理带来的问题中,“更新丢失”通常是应该完全避免的。但防止更新丢失,并不能单靠数据库事务控制器来解决,需要应用程序对要更新的数据加必要的锁来解决,因此,防止更新丢失应该是应用的责任。
“脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。数据库实现事务隔离的方式,基本上可分为以下两种。
1、一种是在读取数据前,对其加锁,阻止其他事务对数据进行修改。
2、另一种是不用加任何锁,通过一定机制生成一个数据请求时间点的一致性数据快照(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不会加任何锁;事务可以通过以下语句显示给记录集加共享锁或排他锁。
1、共享锁(s):select * from table_name where … lock in share mode。
2、排他锁(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 rows in 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 rows in set (0.01 sec)
myisam写锁实验:
对myisam表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;对myisam表的写操作,则会阻塞其他用户对同一表的读和写操作;myisam表的读操作与写操作之间,以及写操作之间是串行的!根据如表20-2所示的例子可以知道,当一个线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。
user1:
mysql> lock table film_text write;
当前session对锁定表的查询、更新、插入操作都可以执行:
mysql> select film_id,title from film_text where film_id = 1001;
user2:
mysql> select film_id,title from film_text where film_id = 1001;
等待
user1:
释放锁:
mysql> unlock tables;
user2:
获得锁,查询返回:
innodb存储引擎的共享锁实验
user1: mysql> set autocommit = 0; user2: mysql> set autocommit = 0;
user1:
当前session对actor_id=178的记录加share mode 的共享锁:
mysql> select actor_id,first_name,last_name from actor where actor_id = 178lock in share mode;
user2:
其他session仍然可以查询记录,并也可以对该记录加share mode的共享锁:
mysql> select actor_id,first_name,last_name from actor where actor_id = 178lock in share mode;
user1:
当前session对锁定的记录进行更新操作,等待锁:
mysql> update actor set last_name = ‘monroe t’ where actor_id = 178;
等待
user2:
其他session也对该记录进行更新操作,则会导致死锁退出:
mysql> update actor set last_name = ‘monroe t’ where actor_id = 178;
error 1213 (40001): deadlock found when trying to get lock; try restarting transaction
user1:
获得锁后,可以成功更新:
mysql> update actor set last_name = ‘monroe t’ where actor_id = 178; query ok, 1 row affected (17.67 sec) rows matched: 1 changed: 1 warnings: 0
innodb存储引擎的排他锁例子
user1: mysql> set autocommit = 0; user2: mysql> set autocommit = 0;
user1:
当前session对actor_id=178的记录加for update的排它锁:
mysql> select actor_id,first_name,last_name from actor where actor_id = 178 for update;
user2:
其他session可以查询该记录,但是不能对该记录加共享锁,会等待获得锁:
mysql> select actor_id,first_name,last_name from actor where actor_id = 178;
user1:
当前session可以对锁定的记录进行更新操作,更新后释放锁:
mysql> update actor set last_name = ‘monroe t’ where actor_id = 178;
user2:
其他session获得锁,得到其他session提交的记录:
mysql> select actor_id,first_name,last_name from actor where actor_id = 178 for update;
更新性能优化的几个重要参数
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支持.根据实际情况调大,一般默认够用了。
以上就是mysql数据库中锁机制的详细介绍的详细内容。
