mysql数据库中锁机制的示例分析

53次阅读
没有评论

共计 7430 个字符,预计需要花费 19 分钟才能阅读完成。

自动写代码机器人,免费开通

这篇文章主要介绍 mysql 数据库中锁机制的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!

悲观锁与乐观锁:
悲观锁:顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会 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 数据库中锁机制的示例分析”这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注丸趣 TV 行业资讯频道!

向 AI 问一下细节

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-12-04发表,共计7430字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)