MySQL从入门到精通(九) MySQL锁,各种锁

MySQL从入门到精通(九) MySQL锁,各种锁,第1张

锁是计算机协调多个进程或线程并发访问某一资源的机制,在数据库中,除传统的计算资源(CPU、RAM、I/O)争用外,数据也是一种供许多用户共享的资源,如何保证数据并发访问的一致性,有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素,从这个角度来说,锁对数据库而言是尤其重要,也更加复杂。MySQL中的锁,按照锁的粒度分为:1、全局锁,就锁定数据库中的所有表。2、表级锁,每次 *** 作锁住整张表。3、行级锁,每次 *** 作锁住对应的行数据。

全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML的写语句,DDL语句,已经更新 *** 作的事务提交语句都将阻塞。其典型的使用场景就是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。但是对数据库加全局锁是有弊端的,如在主库上备份,那么在备份期间都不能执行更新,业务会受影响,第二如果是在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志,会导致主从延迟。

解决办法是在innodb引擎中,备份时加上--single-transaction参数来完成不加锁的一致性数据备份。

添加全局锁: flush tables with read lock解锁 unlock tables。

表级锁,每次 *** 作会锁住整张表.锁定粒度大,发送锁冲突的概率最高,并发读最低,应用在myisam、innodb、BOB等存储引擎中。表级锁分为: 表锁、元数据锁(meta data lock, MDL)和意向锁。

表锁又分为: 表共享读锁 read lock、表独占写锁write lock

语法: 1、加锁 lock tables 表名 ... read/write

2、释放锁 unlock tables 或者关闭客户端连接

注意: 读锁不会阻塞其它客户端的读,但是会阻塞其它客户端的写,写锁既会阻塞其它客户端的读,又会阻塞其它客户端的写。大家可以拿一张表来测试看看。

元数据锁,在加锁过程中是系统自动控制的,无需显示使用,在访问一张表的时候会自动加上,MDL锁主要作用是维护表元数据的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入 *** 作。为了避免DML和DDL冲突,保证读写的正确性。

在MySQL5.5中引入了MDL,当对一张表进行增删改查的时候,加MDL读锁(共享);当对表结构进行变更 *** 作时,加MDL写锁(排他).

查看元数据锁:

select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema_metadata_locks

意向锁,为了避免DML在执行时,加的行锁与表锁的冲突,在innodb中引入了意向锁,使得表锁不用检查每行数据是否加锁,使用意向锁来减少表锁的检查。意向锁分为,意向共享锁is由语句select ... lock in share mode添加。意向排他锁ix,由insert,update,delete,select。。。for update 添加。

select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from performance_schema.data_lock

行级锁,每次 *** 作锁住对应的行数据,锁定粒度最小,发生锁冲突的概率最高,并发读最高,应用在innodb存储引擎中。

innodb的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁,对于行级锁,主要分为以下三类:

1、行锁或者叫record lock记录锁,锁定单个行记录的锁,防止其他事物对次行进行update和delete *** 作,在RC,RR隔离级别下都支持。

2、间隙锁Gap lock,锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事物在这个间隙进行insert *** 作,产生幻读,在RR隔离级别下都支持。

3、临键锁Next-key-lock,行锁和间隙锁组合,同时锁住数据,并锁住数据前面的间隙Gap,在RR隔离级别下支持。

innodb实现了以下两种类型的行锁

1、共享锁 S: 允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。

2、排他锁 X: 允许获取排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。

insert 语句 排他锁 自动添加的

update语句 排他锁 自动添加

delete 语句 排他锁 自动添加

select 正常查询语句 不加锁 。。。

select 。。。lock in share mode 共享锁 需要手动在select 之后加lock in share mode

select 。。。for update 排他锁 需要手动在select之后添加for update

默认情况下,innodb在repeatable read事务隔离级别运行,innodb使用next-key锁进行搜索和索引扫描,以防止幻读。

间隙锁唯一目的是防止其它事务插入间隙,间隙锁可以共存,一个事务采用的间隙锁不会阻止另一个事务在同一间隙上采用的间隙锁。

在我们系统中有一张表它的查询概率非常高。 最近有个需求,需要对这个表增加一个字段 ,然而在增加字段的时候发现系统中有多个业务出现了超时 *** 作,那么这个是什么原因导致的呢?经过查阅资料发现是数据库的 MDL锁+事务导致 的。

MDL锁属于表级别的元数据锁。 表级别锁分为数据锁和元数据锁,通常我们说的加锁一般指的是加的数据锁。跟数据锁一样,元数据锁也分读锁和读写锁。

MDL不需要显示使用,在进行表 *** 作时会自动加上 。当对表进行增删改查时,会自动加上MDL读锁;当要对表进行加减字段的结构修改时,会自动加上MDL写锁。

MDL锁的存在,其实是为了保证数据的一致性。 想象一下,假如没有MDL锁,一个查询在遍历表数据的过程中,另外一个线程执行了ALTER TABLE t DELETE COLUMN 'col_1'把col_1这一列删掉了,那查询结果就乱了,结果中是否应该有这一列数据?

:表示正常往下执行

:表示卡住了,即无法往下执行。

解释:

步骤 1 2 正常执行。执行步骤2 时,会申请表customer的MDL的 SHARED_READ 锁。

步骤3 会卡住, 因为此时会申请表customer的MDL的EXCLUSIVE锁,但是事物一的事物没有提交,此时是无法申请到EXCLUSIVE锁,因为它们是互斥的。

步骤4 也会卡住,因为EXCLUSIVE锁和SHARE_READ锁是互斥的, 且EXCLUSIVE锁的优先级更高 ,所以步骤4 也会卡住。

步骤5 事物提交,释放表的SHARE_READ锁,之后就可以执行6 和7 的 *** 作了。

如果先执行事务二,在执行事务三,则是可以成功的,因为alter数据ddl语句,和事物无关。

因此在我们开发的过程中,需要避免大的事务 *** 作,防止占有锁的时间过长。

MySQL 中有哪些锁?

数据库中锁的设计初衷处理并发问题,作为多用户共享资源,当出现并发访问的时候,数据库需要合理控制资源访问规则。锁就是实现这些访问规则中的重要数据。

锁的分类

根据加锁范围,MySQL 里面的锁可以分成 全局锁 表级锁 行锁 三类。

全局锁

全局锁,就是对整个数据库实例加锁,MySQL 提供了一个加全局读锁的方法,命令是:

Flush tables with read lock (FTWRL)

当需要整个库只读状态的时候,可以使用这个命令,之后其他线程的:数据更新语句(增删改),数据定义语句(建表,修改表结构)和更新事务的提交语句将会被阻塞。

全局锁的使用场景

全局锁的定型使用场景,做 全库逻辑备份 。也就是把整个库每个表都 Select 出来,然后存成文本。

如何整个库都只读,会有什么问题? 如果你在主库上备份,那么在备份期间都不能执行更想,业务就基本上停摆。 如果在从库上备份,那么备份期间从库不能执行主库同步过来的 binlog ,会导致从延迟。 既然要全库只读, 为什么不使用set global readonly=true的方式呢?

readonly 方式也可以让全库进入只读状态,但我还是会建议你用FTWRL方式, 主要有两个原因:

一是, 在有些系统中, readonly的值会被用来做其他逻辑,比如用来判断一个库是主库还是备库。因此,修改global变量的方式影响面更大, 我不建议你使用。 二是, 在异常处理机制上有差异。如果执行FTWRL命令之后由于客户端发生异常断开, 那么MySQL会自动释放这个全局锁, 整个库回到可以正常更新的状态。而将整个库设置为readonly之后, 如果客户端发生异常, 则数据库就会一直保持readonly状态, 这样会导致整个库长时间处于不可写状态, 风险较高 表级别锁

MySQL 里面表级别的锁有两种:一种是表锁,一种是元数据锁(meta data lok, MDL)。表锁的语法是 :

lock tables ... read/write

与 FTWRL 类似,可以使用 unlock tables 主动释放锁,也可以在客户端断开的时候自动释放。需要注意的是,lock tables语法除了会限制别的线程的读写外,也限定了本线程接下来的 *** 作对象。

MDL 表级锁

MDL 不需要显示使用,在访问一个表的时候自动加上, MDL 保证读写的正确性,也就是说在查询数据时,不允许有其他线程对这个表结构做变更。

什么 *** 作会加 MDL 锁?

在MySQL 5.5版本中引入了MDL, 当对一个表做增删改查 *** 作的时候,加 MDL读锁 ;当要对表做结构变更 *** 作的时候,加 MDL写锁

读锁之间不互斥,因此可以有多个线程同时对一张表增删改查。 读写之间、写锁之间是互斥的,用来保证变更表结构 *** 作的安全性,如果有两个线程要同时给一个表加字段,其中一个要等另外一个执行完才能执行。 更改表结构要注意哪些?

给一个表加字段, 或者修改字段, 或者加索引, 需要扫描全表的数据。在对大表 *** 作的时候, 你肯定会特别小心, 以免对线上服务造成影响。而实际上, 即使是小表, *** 作不慎也会出问题,导致整个库的线程爆满。

举个例子

我们来看一下下面的 *** 作序列, 假设表t是一个小表。

image

session A先启动, 这时候会对表t加一个 MDL读锁 。由于session B需要的也是 MDL读锁 , 因此可以正常执行。 session C会被blocked, 是因为session A的MDL读锁还没有释放, 而session C需要MDL写锁, 因此只能被阻塞,读写锁互斥。 如果只有session C自己被阻塞还没什么关系, 但是之后所有要在表t上新申请MDL读锁的请求也会被session C阻塞。前面我们说了,所有对表的增删改查 *** 作都需要先申请MDL读锁, 就都被锁住, 等于这个表现在完全不可读写了。

如果某个表上的查询语句频繁, 而且客户端有重试机制,也就是说超时后会再起一个新session 再请求的话, 这个 库的线程很快就会爆满 。事务中的MDL锁, 在语句执行开始时申请, 但是语句结束后并不会马上释放, 而会等到整个事务提交后再释放。

怎么解决这个 更改表结构问题

比较理想的机制是, 在alter table语句里面设定等待时间, 如果在这个指定的等待时间里面能够拿到MDL写锁最好, 拿不到也不要阻塞后面的业务语句, 先放弃。

ALTER TABLE tbl_name NOWAIT add column ... ALTER TABLE tbl_name WAIT N add column ...


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/7205396.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-02
下一篇 2023-04-02

发表评论

登录后才能评论

评论列表(0条)

保存