MySQL高级-事务与锁的机制

锁的概述

定义

锁是计算机协调多个进程或线程并发访问某一资源的机制

在数据库中,除了传统的计算机资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供许多用户共享的资源

如何保证数据并发访问一致性,有效性,是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素,从这个角度说,锁对数据库而言显得尤为重要,也更加复杂。

生活中的例子

比如你购物,物品库存只有一件了,有别人在和你同时抢购,那么这到底是你抢到了还是他抢到了。

这里肯定要用到事务,我们先从库存表中取出物品数量,然后插入订单,付款后插入付款表信息,然后更新商品的数量。

这个过程中,使用锁可以对有限的资源进行保护,解决隔离和并发的矛盾。

数据库锁的分类

行锁和表锁。

为了尽可能提高数据库的并发度,我们每次锁定的数据范围越小越好,理论上只锁定当前操作数据可以得到最大并发度,但是,管理锁是很消耗资源的一件事情,比如要涉及获取,检查,释放等动作,因此数据库得在系统性能和并发响应两方面进行平衡,这样就产生了锁粒度(Lock granularity)的概念。

一种提高共享资源并发性的方式是让锁定对象更有选择性,尽量只锁定需要修改部分的数据,而不是所有资源。更理想的是只会对修改的数据片,进行精确的锁定。任何时候,在给定的资源上,锁定的数据越少,则系统的并发程度高,只要相互之间不发生冲突即可。

MySQL中的事务

什么是MySQL中的事务?

MySQL 中的事务主要是用于处理操作量大,复杂度高的数据。

事务是由一系列对数据的访问与更新操作组成的程序执行逻辑单元,以便服务器保证数据的完整性。

事务是数据库系统区别于其它一切文件系统的重要特征之一。

归纳总结一下就是

  • 事务就是用户定义的一个数据库操作序列,这些操作要么全做要么全不做,是一个不可分割的工作单位,只有事务中所有的语句都成功的执行了,才可以说这个事务被成功的执行!使用事务以便于服务器保证数据的完整性,MySQL事务主要用于处理操作量大复杂度高的数据

事务的四大属性(ACID)

原子性(Atomicity)

原子性,事务是最小的操作序列单元,一个事务中包含的所有操作在一次执行后要么全部操作成功,要么全部操作失败,也就是说事务执行的过程中出错,那么就会回滚到事务的开始前的状态。

undo log 称之为回滚日志,每条数据的变化(inser/update/delete)都会产生一条记录,并且日志持久化到磁盘,undo log 用来记录数据修改前的信息,==比如说要插入一条记录,那么undo log 就会记录一条删除该信息的语句==,这样需要回滚事务的时候,那么 undo log 就会执行删除你之前插入的那条记录,达到没有修改前的状态,更新一个记录也会生成一条sql记录你更新前的字段状态,从而实现了原子性

一致性(Consistency)

一致性,事务开始前和结束后,数据的完整性约束没有被破坏,比如A向B转账,不可能A扣了钱,B却没收到,其实一致性也是因为原子性而产生的一种表现。

一致性可以封装状态改变(除非它是一个只读的)。事务必须始终保持系统处于一致状态,不管在任何给定的时间并发事务有多少。

一致性的特点

原子性、持久性、隔离性保证了一致性

如果一个操作触发辅助操作(级联,触发器),这些也必须成功,否则交易失败。

如果系统是由多个节点,一致性规定所有的变化必须传播到所有节点(多主复制),如果从站节点是异步更新,那么我们打破一致性规则,系统成为“最终一致性”。

一个事务是数据状态的切换,因此,如果事务是并发多个,系统也必须如同串行事务一样操作。

在现实中,事务系统遭遇并发请求时,这种串行化是有成本的,Amdahl法则对序列串行执行和并发之间的关系的描述如下:“一个程序在并行计算情况下使用多个处理器能提升的速度是由这个程序中串行执行部分的时间决定的。”

大多数数据库管理系统选择(默认情况下)是放宽一致性,以达到更好的并发性。

隔离性(Isolation)

隔离性同一时间,只允许同一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正从一张银行卡里取钱,在A取钱的过程结束之前,B不能向这张卡转账,串行化

事务是并发控制机制,他们交错使用时也能提供一致性,隔离让我们隐藏来自外部世界未提交的状态变化,一个失败的事务不应该破坏系统的状态,隔离是通过用悲观锁或乐观锁机制实现的

隔离的实现

每次修改对应的一个事务ID:row trx id,还对应一个 undo log,因此 undo logrow trx id一一对应的。

持久性(Durability)

持久性事务完成之后,事务对数据库的所有更新将被保存到数据库不能回滚

一个成功的事务将永久性的改变系统的状态,所以在他结束之前,所有导致状态的变化都会记录在一个持久的事务日志之中,如果我们的系统突然系统崩溃或断电,那么所有未完成已提交的事务可能会重演(不会导致数据丢失)。

保证了MySQL数据库的**高可靠性(High Reliability),而不是高可用性(High Availability)。**

事务的回滚错误理解:只要把事务写出来,最后用commit提交一下,数据库会自动判断这些语句是否全部执行成功,如果成功则把所有的数据插入到数据库,如果有一条失败就自动回滚至原始状态!

事务的回滚正确理解是:如果事务中所有的sql语句执行正确,则需要自己手动提交commit,否则有任何一条执行错误,需要自己提交一条rollback,这时才会回滚所有操作,而不是commit会给你自动判断和回滚。

MySQLInnoDB引擎,使用 Redo log 保证了事务的持久性,当事务提交时,必须先将事务的所有日志写入日志文件进行持久化,就是我们常说的 **WAL(write ahead log)**机制,这样才能保证断电或宕机等情况发生后,已提交的事务不会丢失,这个能力成为 crash-safe

Redo log 包括两部分,重做日志缓冲(redo log buffer)重做日志文件(redo log file),前者是易失的缓存,后者是持久化的文件。

并发事务处理带来的问题(重点)

更新丢失(Lost Update)

当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题(最后的更新覆盖了由其它事务做的更新)。

如何解决呢?我们可以在一个事务完成并提交之前,另一个事务不能访问同一行。

脏读(Dirty Reads)

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

一句话:事务A读取到了事务B已修改但尚未提交的数据,还在这个数据基础上做了操作。此时,如果B事务回滚,A读取的数据无效,不符合一致性要求。

不可重复读(Non-Repeatable Reads)

在一个事务内,多次读同一个数据。在这个事务还没结束的时,另一个事务也访问该同一数据。那么,在第一个事务的两次读数据之间。由于第二个事务的修改,那么第一个事务读到的数据可能不一样,这样就发生了在一个事务内两次读到的数据是不一样的,因此称为不可重复度,即原始读取不可重复。

一句话:一个事务范围内两个相同的查询却返回了不同数据。

幻读(Phantom Reads)

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

一句话:事务A读取到了事务B提交的新增数据,不符合隔离性。

事务的隔离级别

脏读、不可重复读、幻读,其实都是**数据库一致性导致的问题**,必须由数据库提供一定的事务隔离机制来解决。

MySQL数据库的是 可重复读(Repeatable read)

读数据一致性允许的并发副作用隔离级别读数据一致性脏读不可重复读幻读
读未提交(Read uncommitted)最低的级别,只能保证不读取物理上损坏的数据
读已提交(Read committed)语句级别
可重复读(Repeatable read)事务级别
可序列化(Serializable)最高级别,事务级别

数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然是与“并发”矛盾的,同时,不同的应用对读一致性常常使看当前数据库的事务隔离级别 show variables like 'tx_isolation'

三锁

表锁(偏读锁)

特点

偏向MyISAM存储引擎,开销小,加锁快;无死锁,锁定粒度大,发生锁冲突的概率最高,并发度最低。

MyISAM引擎来说,读锁会阻塞写,但是不会阻塞读,而写锁则会把读和写都阻塞

案例分析

建表SQL
create table mylock (
     id int not null primary key auto_increment,
     name varchar(20)
) engine myisam;

insert into mylock(name) values('a');
insert into mylock(name) values('b');
insert into mylock(name) values('c');
insert into mylock(name) values('d');
insert into mylock(name) values('e');

select * from mylock;
加读锁

写锁反之。

-- 给当前会话加 mylock 表的读锁
-- 代表着当前会话可以查看该表的记录,但是没办法查看别的没有锁定的表
-- 当前会话中插入或者更新锁定的表都会提示错误
lock table mylock read;

-- 解锁
unlock tables;

表锁分析

-- 查看哪些表被加锁了
show open tables;

如何分析表锁定

可以通过检查 table_locks_waitedtable_locks_immediate 状态来分析系统上的表锁定:

show status like 'table%';

table_locks_waited:产生表级锁定的次数,表示可以立即获取锁的查询次数,每次立即获取锁之后次数加一。

table_locks_immediate:出现表级锁定争用而发生等待的次数(不能立即获取锁的次数,每次等待次数加一),此值高的话说明存在着较严重表级锁争用情况。

衍生总结

MyISAM引擎读写锁调度是写优先,这也是MyISAM不适合做写为主表的引擎。因为写锁之后,其它线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞。

行锁

特点

偏向InnoDB存储引擎,开销大,加锁慢,会出现死锁,锁定力度最小,发生锁冲突的概率最低,并发度也最高。

InnoDB与MyISAM的最大不同有两点,支持事务!采用行级锁!

案例

建表SQL
create table test_innodb_lock (a int(11),b varchar(16))engine=innodb;

insert into test_innodb_lock values(1,'b2');
insert into test_innodb_lock values(3,'3');
insert into test_innodb_lock values(4,'4000');
insert into test_innodb_lock values(5,'5000');
insert into test_innodb_lock values(6,'6000');
insert into test_innodb_lock values(7,'7000');
insert into test_innodb_lock values(8,'8000');
insert into test_innodb_lock values(9,'9000');
insert into test_innodb_lock values(1,'b1');

create index test_innodb_a_ind on test_innodb_lock(a);
create index test_innodb_lock_b_ind on test_innodb_lock(b);

select * from test_innodb_lock;

码云仓库同步笔记,可自取欢迎各位star指正:https://gitee.com/noblegasesgoo/notes

如果出错希望评论区大佬互相讨论指正,维护社区健康大家一起出一份力,不能有容忍错误知识。
										—————————————————————— 爱你们的 noblegasesgoo
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值