MySQL中InnoDB锁机制介绍及一些测试

INNODB 锁说明:

锁机制是事务型数据库中为了保证数据的一致完整性的特性;在对MySQL进行性能调优时,往往需要重点考虑锁机制对整个事务的影响。MySQL 5.5 版本后InnoDB存储引擎已是默认存储引擎,这里重点介绍InnoDB存储引擎的锁机制。

InnoDB 主要使用两种级别的锁机制: 行级锁和表级锁

Innodb 的行级锁分为两种类型: 共享锁和排他锁

而在锁机制的实现过程中为了 让行级锁定和表级锁定共存 ,Innodb使用了 意向锁(表级锁) 的概念,分 意向共享锁 意向排他锁 这两种。

对于这几种锁模式解释的场景如下:

事务A想读某一行数据,需要给这行数据添加一个共享锁, 此时如果发现这行数据上有一个共享锁锁着,事务A可以继续添加一个共享锁 ,但无法加排他锁。 如果发现这行数据有个排他锁锁着 ,事务A则需要等该锁释放才能进行锁定,此时, 事务A可以在该行数据的表上添加一个意向锁 ,如果需要共享锁,则添加 意向共享锁 ;如果需要排他锁,则需要 添加 意向排他锁

所以,可以说Innodb的锁定模式实际上可以分为四种:共享锁(S),排他锁(X),意向共享锁(IS)和意向排他锁(IX),可以通过以下表格来总结上面这四种所的共存逻辑关系:


共享锁(S)

排他锁(X)

意向共享锁(IS)

意向排他锁(IX)

共享锁(S)

兼容

冲突

兼容

冲突

排他锁(X)

冲突

冲突

冲突

冲突

意向共享锁(IS)

兼容

冲突

兼容

兼容

意向排他锁(IX)

冲突

冲突

兼容

冲突

 

根据对数据上锁所锁定的范围不同,又分了以下三种锁算法类型:

·        记录锁(Record Locks):记录锁锁定索引中 一条记录 。(比如id=5的记录)

·        间隙锁(Gap Locks): 锁定一个范围,但不包含记录本身 (比如本身是id=5,锁住了id=1,2,3,4的记录)

·        Next-Key Locks:Next-Key 锁是索引记录上的记录锁和在索引记录之前的间隙锁的 组合,包含记录本身 。(前两种的组合,比如id=1,2,3,4,5的记录)

 

MYSQL 中如何查看锁:

通过show engine innodb status\G; 查看是否存在锁表情况:

 

通过information_schema.innodb_trx 查看事务情况。

select * from information_schema.innodb_trx\G;

通过information_schema.INNODB_LOCKS表查询锁情况:

select * from information_schema.INNODB_LOCKS\G;

 

通过information_schema.INNODB_LOCK_waits查看锁阻塞情况:

select * from information_schema.INNODB_LOCK_waits\G;

 

 

无索引引起行锁升级表锁实验:

 

会话1:update t表上i=4记录,字段i上没有索引

mysql> start transaction;

会话2:update t表上i=1记录,但此时发现在等待锁。

mysql> start transaction;

由于b列上没有索引,所以在update的时候自动升级为表锁,导致后面的update会话虽然操作的不是同一行,但是仍被堵塞。

通过查询锁情况,这两个会话的情况都对表t请求x模式的锁。

 

mysql> select * from information_schema.innodb_locks\G;

*************************** 1. row ***************************

    lock_id: 8710:107:3:2

lock_trx_id: 8710

   lock_mode: X

  lock_type: RECORD

 lock_table: `cwdtest`.`t`

 lock_index: GEN_CLUST_INDEX

 lock_space: 107

  lock_page: 3

   lock_rec: 2

  lock_data: 0x000000000300

*************************** 2. row ***************************

    lock_id: 8709:107:3:2

lock_trx_id: 8709

  lock_mode: X

  lock_type: RECORD

 lock_table: `cwdtest`.`t`

 lock_index: GEN_CLUST_INDEX

 lock_space: 107

  lock_page: 3

   lock_rec: 2

  lock_data: 0x000000000300

2 rows in set, 1 warning (0.00 sec)

 

 

 

尝试通过GDB调试,抓取会话被阻塞时执行的代码,可以发现最终处于隔离级别的判定,此次需要深入解读具体源代码。

(gdb)  p   *(trx_sys->rw_trx_list->start->lock->trx_locks->start)

$1 = {trx = 0x7f2c0823c980, trx_locks = {prev = 0x0, next =   0x0}, index = 0x0, hash = 0x0, un_member = {tab_lock = {table =   0x7f2b98013468, locks = {prev = 0x0,

        next = 0x0}},   rec_lock = {space = 2550215784, page_no = 32555, n_bits = 0}}, type_mode =   17}

(gdb)  p   trx_sys->rw_trx_list->start->lock->trx_locks->start->un_member->tab_lock->table->name

$2 = {m_name = 0x7f2b98010c88 "cwdtest/t"}

 

5467            } else if   (match_mode == ROW_SEL_EXACT_PREFIX) {

(gdb)

5516            if (prebuilt->select_lock_type   != LOCK_NONE) {

(gdb)

5533                        ||   dict_index_is_spatial(index)) {

(gdb)

5530                        ||   srv_locks_unsafe_for_binlog

(gdb)

5531                        ||   trx->isolation_level <= TRX_ISO_READ_COMMITTED

(gdb)                         // 隔离级别小于等于读已提交

5532                        ||   (unique_search && !rec_get_deleted_flag(rec, comp))                          // 唯一键扫描

(gdb)        

5533                        ||   dict_index_is_spatial(index)) {

(gdb)  

                       

   

此时需要会话1提交完成后,会话2才能正常获取到锁。

 

 

 

因为间隙锁被堵塞:

会话1:

mysql> update t set i=i+9 where i < 4 and i > 1;

Query OK, 1 row affected (0.00 sec)

Rows matched: 1  Changed: 1  Warnings: 0

会话2:

mysql>  insert into t values(4);

 

------------

TRANSACTIONS

------------

Trx id counter 8727

Purge done for trx's   n:o < 8720 undo n:o < 0 state: running but idle

History list length   16

LIST OF TRANSACTIONS   FOR EACH SESSION:

---TRANSACTION   421302068564688, not started

0 lock struct(s),   heap size 1136, 0 row lock(s)

---TRANSACTION 8726,   ACTIVE 194 sec inserting

mysql tables in use   1, locked 1

LOCK WAIT 3 lock   struct(s), heap size 1136, 2 row lock(s), undo log entries 1

MySQL thread id 4, OS   thread handle 139826788828928, query id 76 localhost root update

insert into t   values(4)

------- TRX HAS BEEN   WAITING 5 SEC FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 107   page no 4 n bits 80 index i of table `cwdtest`.`t` trx id 8726 lock_mode X   locks gap before rec insert intention waiting

------------------

---TRANSACTION 8725,   ACTIVE 208 sec

4 lock struct(s),   heap size 1136, 5 row lock(s), undo log entries 1

MySQL thread id 5, OS   thread handle 139826788558592, query id 71 localhost root

--------

 

 

 

mysql> select *   from information_schema.innodb_locks\G;

***************************   1. row ***************************

    lock_id: 8726:107:4:3

lock_trx_id: 8726

  lock_mode: X,GAP

  lock_type: RECORD

 lock_table: `cwdtest`.`t`

 lock_index: i

 lock_space: 107

  lock_page: 4

   lock_rec: 3

  lock_data: 5, 0x000000000304

***************************   2. row ***************************

    lock_id: 8725:107:4:3

lock_trx_id: 8725

  lock_mode: X

  lock_type: RECORD

 lock_table: `cwdtest`.`t`

 lock_index: i

 lock_space: 107

  lock_page: 4

   lock_rec: 3

  lock_data: 5, 0x000000000304

2 rows in set, 1   warning (0.00 sec)

 

ERROR:

No query specified

 

 

由于行级锁产生时是通过在指向数据记录的第一个索引键之前和最后一个索引键之后的空域空间上标记锁定信息而实现的,因此在过滤条件中含有范围查询,会造成范围内的行都被锁定。

 

从上述的实验来看,Innodb引擎行级锁的实现效果远远不及Oracle,由于行级锁的实现严重依赖于索引,所以在SQL优化时需要重点关注索引的信息。

  因此,针对Innodb的优化建议,可以简单归纳为以下几点:

(一)尽可能让所有的查询都通过索引来完成,从而避免Innodb因为无法通过索引键加锁而升级为表级锁定;

(二)避免大事务的产生,减少被锁的数据、索引和锁定时间长度;

(三)减少基于范围的数据查询过滤条件,避免因为间隙锁带来的负面影响而锁定了不该锁定的记录;

 

死锁实验:

mysql> use cwdtest;

Database changed

 

mysql> START TRANSACTION;

Query OK, 0 rows affected (0.00 sec)

 

mysql> SELECT * FROM t WHERE i = 1 LOCK IN SHARE MODE;

+------+

| i    |

+------+

|    1 |

+------+

1 row in set (0.00 sec)

 

 

mysql> use cwdtest;

Reading table information for completion of table and column names

You can turn off this feature to get a quicker startup with -A

 

Database changed

mysql> START TRANSACTION;

Query OK, 0 rows affected (0.00 sec)

 

mysql> DELETE FROM t WHERE i = 1;

ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

mysql>

 

 

 



来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/29863023/viewspace-2216730/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/29863023/viewspace-2216730/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值