MSQL锁及事务

目录

一、MyISAM表锁

二、如何加表级锁

三、并发插入

四、MyISAM 的锁调度

五、InnoDB锁

InnoDB 与 MyISAM 的最大不同有两点:

一是支持事务(TRANSACTION);

二是采用了行级锁。行级锁与表级锁本来就有许多不同之处;

1、因为InnoDB支持事务,先说ACID

2、事务带来的问题

3、事务隔离级别

 

MyISAM和MEMORY存储引擎采用的是表级锁(table-level locking);BDB
存储引擎采用的是页面锁(page-level locking),但也支持表级锁;InnoDB存储引擎既支持行
级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。
MySQL这3种锁的特性可大致归纳如下。

  •  表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
  • 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
  •   页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。

一、MyISAM表锁

用来查看表锁争夺,若Table_locks_waited数值较大,则说明存在严重锁争夺

(图片来自PDF) 

MyISAM 表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;

对 MyISAM 表的写操作,则会阻塞其他用户对同一表的读和写操作;

MyISAM 表的读操作与写操作之间,以及写操作之间是串行的

二、如何加表级锁

给 MyISAM 表显示加锁,一般是为了在一定程度模拟事务操作,实现对某一时间点多个表的一致性读取。例如,有一个订单表 orders,其中记录有各订单的总金额 total,同时还有一个订单明细表 order_detail,其中记录有各订单每一产品的金额小计 subtotal,假设我们需要检查这两个表的金额合计是否相符,可能就需要执行如下两条 SQL:


Select sum(total) from orders;
Select sum(subtotal) from order_detail;


这时,如果不先给两个表加锁,就可能产生错误的结果,因为第一条语句执行过程中,
order_detail 表可能已经发生了改变。因此,正确的方法应该是:


Lock tables orders read local, order_detail read local;
Select sum(total) from orders;
Select sum(subtotal) from order_detail;
Unlock tables;


要特别说明以下两点内容。
a : 上面的例子在 LOCK TABLES 时加了“local”选项,其作用就是在满足 MyISAM 表并发插入条件的情况下,允许其他用户在表尾并发插入记录。
b : 在用 LOCK TABLES 给表显式加表锁时,必须同时取得所有涉及到表的锁,并且 MySQL 不支持锁升级。也就是说,在执行 LOCK TABLES 后,只能访问显式加锁的这些表,不能访问未加锁的表;同时,如果加的是读锁,那么只能执行查询操作,而不能执行更新操作。其实,在自动加锁的情况下也基本如此,MyISAM 总是一次获得 SQL 语句所需要的全部锁。这也正是 MyISAM 表不会出现死锁(Deadlock Free)的原因。

三、并发插入

MyISAM存储引擎有一个系统变量concurrent_insert,专门用以控制其并发插入的行为,其值
分别可以为0、1或2。


A:  当concurrent_insert设置为0时,不允许并发插入。


B:  当concurrent_insert设置为1时,如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录。这也是MySQL的默认设置。


C:  当concurrent_insert设置为2时,无论MyISAM表中有没有空洞,都允许在表尾并发插入记录。

 可以利用MyISAM存储引擎的并发插入特性,来解决应用中对同一表查询和插入的锁争用。
例如,将concurrent_insert系统变量设为2,总是允许并发插入;同时,通过定期在系统空闲
时段执行OPTIMIZE TABLE语句来整理空间碎片,收回因删除记录而产生的中间空洞。

四、MyISAM 的锁调度

MyISAM 存储引擎的读锁和写锁是互斥的,读写操作是串行的。那么,一个进程请求某个 MyISAM 表的读锁,同时另一个进程也请求同一表的写锁,MySQL 如何处理呢?
答案是写进程先获得锁。不仅如此,即使读请求先到锁等待队列,写请求后到,写锁也会插
到读锁请求之前
!这是因为 MySQL 认为写请求一般比读请求要重要。这也正是 MyISAM 表不太适合于有大量更新操作和查询操作应用的原因,因为,大量的更新操作会造成查询操作很难获得读锁,从而可能永远阻塞。这种情况有时可能会变得非常糟糕!幸好我们可以通过一些设置来调节 MyISAM 的调度行为。

  • 通过指定启动参数low-priority-updates,使MyISAM引擎默认给予读请求以优先的权利。
  •  通过执行命令SET LOW_PRIORITY_UPDATES=1,使该连接发出的更新请求优先级降低。
  •  通过指定INSERT、UPDATE、DELETE语句的LOW_PRIORITY属性,降低该语句的优先级。

虽然上面 3 种方法都是要么更新优先,要么查询优先的方法,但还是可以用其来解决查询相对重要的应用(如用户登录系统)中,读锁等待严重的问题。
另外,MySQL也提供了一种折中的办法来调节读写冲突,即给系统参数max_write_lock_count设置一个合适的值,当一个表的读锁达到这个值后,MySQL就暂时将写请求的优先级降低,给读进程一定获得锁的机会。

五、InnoDB锁

InnoDB 与 MyISAM 的最大不同有两点:

一是支持事务(TRANSACTION);

二是采用了行级锁。行级锁与表级锁本来就有许多不同之处;

1、因为InnoDB支持事务,先说ACID

  • 原子性(Atomicity):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
  •   一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如 B 树索引或双向链表)也都必须是正确的。
  •   隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。
  • 持久性(Durable):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。

2、事务带来的问题

  • 更新丢失(Lost Update):当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题最后的更新覆盖了由其他事务所做的更新。一个事务处理时,避免另外事务加入,则可避免此问题。

  • 脏读(Dirty Reads):一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象地叫做"脏读"。

 

  •  不可重复读(Non-Repeatable Reads):一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读”。

 

  •  幻读(Phantom Reads):一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。

3、事务隔离级别

1、数据库实现隔离的方式一般为两种

1)、一种是在读取数据前,对其加锁,阻止其他事务对数据进行修改。

2)、 另一种是不用加任何锁,通过一定机制生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供一定级别(语句级或事务级)的一致性读取。
从用户的角度来看,好象是数据库可以提供同一数据的多个版本,因此,这种技术叫做数据多版本并发控制(MultiVersion Concurrency Control,简称 MVCC 或MCC),也经常称为多版本数据库。

数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的。同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关心数据并发访问的能力。

 2、获取InnoDB锁的争用情况

如果争用情况严重, InnoDB_row_lock_waits 和 InnoDB_row_lock_time_avg 的值
比较高

3、InnoDB 的行锁模式及加锁方法

两种行锁

  •  共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
  • 排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。

两种表锁

  •  意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的 IS 锁。
  •  意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的 IX 锁。

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

4、行锁实现方式

InnoDB 行锁是通过给索引上的索引项加锁来实现的,这一点 MySQL 与 Oracle 不同,后者是通过在数据块中对相应数据行加锁来实现的。InnoDB 这种行锁实现特点意味着:只有通过索引条件检索数据,InnoDB 才使用行级锁,否则,InnoDB 将使用表锁!
在实际应用中,要特别注意 InnoDB 行锁的这一特性,不然的话,可能导致大量的锁冲突,
从而影响并发性能。下面通过一些实际例子来加以说明。
(1)在不通过索引条件查询的时候,InnoDB 确实使用的是表锁,而不是行锁。

(2)由于 MySQL 的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行
的记录,但是如果是使用相同的索引键,是会出现锁冲突的。应用设计的时候要注意这一点

(3)当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论
是使用主键索引、唯一索引或普通索引,InnoDB 都会使用行锁来对数据加锁

(4)即便在条件中使用了索引字段,但是否使用索引来检索数据是由 MySQL 通过判断不同
执行计划的代价来决定的,如果 MySQL 认为全表扫描效率更高,比如对一些很小的表,它
就不会使用索引,这种情况下 InnoDB 将使用表锁,而不是行锁。因此,在分析锁冲突时,
别忘了检查 SQL 的执行计划,以确认是否真正使用了索引。

后面还会有间隙锁,锁使用等,

 

 


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

talNot

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值