mysql innodb锁开销为什么很小_MySQL锁问题-InnoDB的锁与事务

一 综述

MySQL 的锁机制相较其他的数据库比较简单,最显著的特点是不同的存储引擎支持不同的锁机制。主要有三种类型的锁

表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生冲突的概率最高,并发度最低。支持表级锁的代表是 MyISAM 引擎。

行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度最高。支持行级锁的代表是 InnoDB 引擎。

页面锁:开销和加锁时间介于表级锁和行级锁之间;会出现死锁;锁定粒度介于表级锁和行级锁之间。支持页面锁代表是 BDB 引擎。

就锁本身的特点来说,各有优势。所以具体采用什么级别的锁还是要结合实际的应用场景。比如大量查询为主,少量以索引条件的更新数据应用适合表级锁;而又大量按索引执行更新操作的应用则适合行级锁。

参考文章

二 MyISAM 表锁

1 查询表级锁争用情况

show status like 'table%';

如果结果字段 Table_locks_waited 的值比较高,则说明存在比较严重的锁争用。

2 表级锁的锁模式

表共享锁(Table Read Lock)

表独占写锁(Table Write Lock)

锁之间是兼容情况可以用一句话阐述:共享锁与任何锁兼容,而独占锁与任何锁都不兼容。

因此对 MyISAM 的读操作不会阻塞其他用户对同一张表的读请求,但是会阻塞对同一个表的写请求;对应的写操作会阻塞其他用户对同一张表的读/写操作。

表的读操作与写操作之间,以及写操作与写操作之间都是串行的。 当一个线程获得一个表的写锁之后,只有持有锁的线程可以对表进行更新操作,其他线程的读、写操作都会等待直到锁被释放。

2.1 应用示例

这是一个“写锁阻塞读操作”的例子

session1 中对表 test_lock 加锁并写入一条数据

lock table test_lock write;

insert into test_lock (id, value) VALUES (1, 'fail');

session2 在 session1 操作过程中(此时并没有释放锁)执行一次查询操作

SELECT * FROM test_lock;

结果发现 session2 的操作进入阻塞状态。此时再切换到 session1 将持有的写锁释放

unlock table test_lock;

释放锁后 session2 的查询操作得到结果。

2.2 如何加表锁

实际上MyISAM在执行查询语句的时候会 自动给涉及到的所有表加读锁,在执行更新操作的时候 会自动给涉及的表加写锁。即这个过程并不需要用户干预,因此用户 一般不需要使用前面示例中的语句来显式的加锁。

在一些特殊的情况下需要我们显式加锁,不过这个是为了模拟事务操作。比如两张表 order 与 order_detail 都有一个金额总计字段,现在需要检查匹配两个表的金额是否一致。

lock tables orders read local, order_detail read local;

select sum(total) from orders;

select sum(subtotal) from orders;

unlock tables;

在用 LOCK TABLE 给表显式加锁时,必须同时获取到所有涉及表的锁,并且 MyISAM 不支持锁升级。

另外,锁定一个表,还要顺带锁定这个表的所有别名,否则会报错。

lock table test_lock as a,test_lock as b;

2.3 并发插入

MyISAM表的读和写操作是串行的,这是总体上来看的。加上部分限制,MyISAM也支持在读的同时并发写。

存储引擎有两个系统变量

concurrent_insert

这个值的可选值有0,1,2三个。

0 设置为 0 表示 不允许并发插入

1 设置为 1 表示 如果表中没有空洞,则允许在一个线程读表的同时,另一个线程从表尾插入。 这也是 MyISAM 的默认选项。

2 设置为 2 表示 不论表中有没有空洞,都允许从表尾进行并发插入操作。

这个特性可以用来处理应用中对同一表查询和插入操作的争用。比如将这个系统变量设置为 2 的时候,总是允许并发插入,同时通过定期在系统空闲时执行 OPTIMIZE TABLE 语句来整理空间碎片,收回空洞。

2.4 MyISAM 的锁调度

一个进程在请求某个 MyISAM 表的读锁,另一个线程此时也正在请求同一个表的写锁,这种情况下总是写锁请求先获得锁,哪怕按照排队顺序读锁在写锁之前。这是 MyISAM 表不适合有大量更新操作和查询操作原因。

不过可以通过一些设置来调节这些调度行为

指定启动参数 low-priority-updates,让引擎默认给予 读请求优先的权利。

执行命令 SET LOW_PRIORITY_UPDATES = 1,使该连接发出的更新其请求优先级降低。

指定 INSERT, UPDATE, DELETE 语句的 LOW_PRIORITY 属性,降低该语句的优先级。

这三种办法都是要么更新优先,要么查询优先的方法,但是还是可以用来解决查询相对重要的场景中读锁等待时间过长的问题。

除此之外还有一种调度调节方法

max_write_lock_count

该参数也可以用来调节读写冲突。设置一个值后,MySQL 在一个表的读锁达到这个值后,暂时将写请求的优先级降低,给读进程一定获得锁的机会。

另外,一些需要长时间运行的查询操作应该尽量避免,如果一定要执行,可以安排到数据库压力不那么大的时间短执行,比如半夜。

三 InnoDB 锁问题

首先 InnoDB 与 MyISAM 之间最大的区别是

支持事务( TRANSACTION )

采用了行级锁

3.1 获取 InnoDB 的行锁争用情况

show status like 'innoDB_row_lock%'

// output

Innodb_row_lock_current_waits 0

Innodb_row_lock_time 181621

Innodb_row_lock_time_avg 321

Innodb_row_lock_time_max 20824

Innodb_row_lock_waits 565

字段名

含义

Innodb_row_lock_current_waits

当前处于等待状态的锁数量

Innodb_row_lock_time

启动到现在锁定的总时间长度

Innodb_row_lock_time_avg

平均每次锁定的时长(ms)

Innodb_row_lock_time_max

最长的一次锁定时间(ms)

Innodb_row_lock_waits

启动到现在总计是锁定次数

如果锁争用比较严重,那么字段 Innodb_row_lock_current_waits 与 Innodb_row_lock_time_avg 值都会比较高。具体的调优手段和参数这里暂且不表,我们继续了解InnoDB锁与其事务间的关系。

背景知识

3.2 行锁模式以及加锁方法

InnoDB 有以下两种类型的锁

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

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

除此之外还有两种意向锁

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

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

2e908f0de8d0

行级锁的兼容

意向锁是InnoDB自动加的,不需要用户干预。UPDATE/DELETE/INSERT 语句 InnoDB 会自动给涉及的数据集加排他锁。普通的 SELECT 语句 InnoDB 不加锁。不过可以在语句中显式的给数据集加共享锁或者排他锁。

在 RC(read commited)级别中,数据的读取都是不加锁的,但是数据的写入、修改和删除是需要加锁的。

InnoDB 行锁是通过给索引项加锁实现的,如果没有索引,InnoDB 将通过隐藏的聚簇索引来对记录加锁。 InnoDB 行锁有三种情形:

Record Lock:对索引项加锁

Gap Lock:对索引之间的“间隙”、第一条记录前的“间隙”或最后一条记录的“间隙”加锁。

Next-key lock:前两种的组合,对记录及其前面的间隙加锁。

如果不通过索引条件检索数据,那么 InnoDB 将对表中的所有记录加锁,实际效果就和表锁一样了。

如下是一些注意事项及说明

1. 在不通过索引条件查询时,InnoDB 会锁定表中的所有记录

// session1

set autocommit = 0;

select * from test where col1 = 1 for update;

// session2

select * from test where col1 = 2 for update;

// waiting...

这是一个串行操作,在session1给表的col1字段的某条记录加了排他锁,在理想情况下session2的操作应该不会受到影响。

如果col1字段没有设置索引的话,这个阻塞操作就一定会发生。因为检索操作没有走到索引会导致 InnoDB 给所有的记录都加了行锁。这样 session2 的排他锁就无法得到从而进入阻塞状态。

2. 由于 MySQL 的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然访问不同行的记录,如果使用了相同的索引键,一样会出现锁冲突。

record1

record2

id=1

value = 1

id=1

value = 4

表中两条记录,两个字段id,value中仅有id字段有索引。

// session1

set autocommit = 0;

select * from table_test where id = 1 and value = '1' for update;

// session2

set autocommit = 0;

select * from table_test where id = 1 and value = '4' for update;

// waiting

虽然session2访问的是和session1不同的记录,但是因为使用了相同的索引,所以也还是需要等待锁。

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

4. 即便在条件中使用了索引字段,但是是否使用索引来检索数据是由 MySQL 通过判断不同执行计划的代价来决定的,如果 MySQL 认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下 InnoDB 也会对所有记录加锁。

3.3 Next-key 锁

当我们使用范围条件而不是相等条件检索数据,请求获得锁,InnoDB 会给符合条件的数据的索引项加锁。

select * from test where id > 100 for update;

这是一个范围检索,InnoDB 不仅会对符合条件的记录加锁,还会对大于100的“间隙(即不存在的记录)”加锁。这个检索执行的时候会阻塞100以后id数据的插入操作。

在使用范围条件检索并锁定记录时,InnoDB 的这种加锁机制会阻塞符合条件范围内键值对的并发插入,会造成严重的锁等待。因此在实际开发中,尤其是并发插入较多的应用,我们要尽量使用相等的条件来访问和更新数据,避免使用范围检索。

四 乐观锁与悲观锁

悲观锁:对“数据被修改”这件事情上,持保守态度。所以在处理数据的过程中会对被涉及到的数据加锁。这种思想能够最大程度上保证事务的隔离性。但是这样的思想本身也会降低设计的并发性。典型的有数据库的锁机制;Java代码中的lock与synchronized关键字。

乐观锁:与悲观锁相反的认知态度。放宽对数据的加锁机制。这里要提一个典型的机制-MVCC(multiVersion Concurrent Control),基于版本的并发控制。

InnoDB 提供了基于 MVCC 的并发控制机制。通过给数据行附加一个版本号来确保数据对更新的敏感。InnoDB 会在每行数据后添加两个额外的隐藏的值来实现MVCC。在实际操作中,存储的并不是时间,而是事务的版本号,每开启一个新事务,事务的版本号就会递增。

SELECT时,读取创建版本号<=当前事务版本号,删除版本号为空或>当前事务版本号。

INSERT时,保存当前事务版本号为行的创建版本号

DELETE时,保存当前事务版本号为行的删除版本号

UPDATE时,插入一条新纪录,保存当前事务版本号为行创建版本号,同时保存当前事务版本号到原来删除的行

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: innodb_flush_log_at_trx_commit 是 MySQLInnoDB 引擎的一个参数。它控制事务提交时日志缓冲区的刷新频率。当该参数设置为 1 时,每次事务提交都会将日志缓冲区刷新到磁盘上,这样可以保证事务的 ACID 特性。如果该参数设置为 0 或 2,则日志缓冲区可能不会立即刷新到磁盘上,这样可以提高性能,但会增加事务数据丢失的风险。 ### 回答2: innodb_flush_log_at_trx_commit 参数用于控制InnoDB存储引擎的日志刷新策略。其含义如下: 1. 参数值为0:表示将日志缓存在内存中,每秒将日志批量刷新到磁盘上。这种设置可以提高写入性能,但在断电等意外情况下可能会造成数据丢失。 2. 参数值为1:表示每次事务提交时都将日志立即刷新到磁盘上。这种设置可以保证在发生故障时不会丢失任何事务的数据,但会对性能产生一定的影响。 3. 参数值为2:表示将日志缓存在内存中,每秒将日志立即刷新到磁盘上。这种设置可以兼顾写入性能和数据的持久性,是默认的值,也是推荐的配置。 当需要强制保证数据完整性和一致性时,可以将该参数设置为1。而在一些对数据实时性要求较高的场景下,可以将该参数设置为0以提高写入性能,但这样会增加数据丢失的风险。对于一般的应用场景,推荐使用默认值2。 需要注意的是,innodb_flush_log_at_trx_commit 参数对事务日志的刷盘行为进行控制,而不是控制数据的持久化。InnoDB存储引擎通过redo log来保证事务的持久性,而innodb_flush_log_at_trx_commit参数决定了什么时候将日志持久化到磁盘,从而影响了事务的持久性保证和写入性能。 ### 回答3: innodb_flush_log_at_trx_commit是MySQL InnoDB存储引擎的一个参数。 innodb_flush_log_at_trx_commit参数的含义是控制InnoDB存储引擎在事务提交时将事务日志(redo log)写入磁盘的时机。 该参数有三个可选值: 1. 0:表示事务提交时不立即将事务日志写入磁盘,而是将日志写入操作系统缓存,然后由操作系统根据自己的策略异步写入磁盘。这种方式具有最高的性能,但最小的数据持久性,即在数据库崩溃时可能会丢失一部分事务数据。 2. 1:表示默认值,事务提交时将事务日志立即写入磁盘。这种方式具有较高的数据持久性,即数据库崩溃时只会丢失最后一次提交事务数据。但写入磁盘的操作会增加IO的开销,可能会影响系统的性能。 3. 2:表示事务提交时将事务日志先写入操作系统缓存,并标记为需要每秒写入磁盘(写back校对点),然后由后台线程按照每秒一次的频率将缓存中的日志写入磁盘。这种方式牺牲了一部分数据的持久性,但在性能和数据持久性之间做了平衡。 根据应用的需求和对数据持久性的要求,可以调整innodb_flush_log_at_trx_commit参数的值,以达到最佳的性能和数据安全性的平衡。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值