MySQL 锁的深入了解


1.排它锁之间的冲突:

排它锁相互之间产生冲突一般是锁等待或死锁,例如两个事务并发去修改同一个表的同一条记录就产生锁等待的事件,开启两个命令窗口执行下面相同的语句:

mysql> set autocommit=0;

Query OK, 0 rows affected

mysql> update t_user_info set update_time = now() where payment_account_no =’6000222′;

然后通过下面语句可以查询当前锁事件的信息:

mysql> show status like’innodb_row_lock%’;

+——————————-+——–+

| Variable_name                 | Value  |

+——————————-+——–+

| Innodb_row_lock_current_waits | 1      |

| Innodb_row_lock_time          | 356727 |

| Innodb_row_lock_time_avg      | 27440  |

| Innodb_row_lock_time_max      | 51405  |

| Innodb_row_lock_waits         | 13     |

+——————————-+——–+

5 rows in set

Innodb_row_lock_current_waits   = 1, 说明有一个事务处于锁等待的事件中,静观过了1分之后,发现Innodb_row_lock_current_waits 将会变成 0 ,因为等待的事务锁等待超时报错回滚了,异常如下:

[Err] 1205 – Lock wait timeout exceeded; try restarting transaction

查看一下锁超时的时间设置, 默认为 50 秒,即  innodb_lock_wait_timeout 的数值, 当然这个数值可以适当调大一点来适应一些长事务的业务处理, 例如100秒。

mysql> show variables like ‘%timeout%’;

+—————————-+———-+

| Variable_name              | Value    |

+—————————-+———-+

| connect_timeout            | 10       |

| delayed_insert_timeout     | 300      |

| innodb_lock_wait_timeout   | 50       |

| innodb_rollback_on_timeout | OFF      |

| interactive_timeout        | 28800    |

| lock_wait_timeout          | 31536000 |

| net_read_timeout           | 30       |

| net_write_timeout          | 60       |

| slave_net_timeout          | 3600     |

| wait_timeout               | 28800    |

+—————————-+———-+

10 rows in set


2.共享锁与排它锁之间的冲突

两个事务并行执行以下两条SQL, 结果就会出锁等待事件。

SQL上共享锁: SELECT  * FROM t_user_info LOCK IN SHARE MODE;

SQL上排它锁: SELECT  * FROM t_user_info FOR UPDATE; 

但是在实践的开发中,共享锁与排它锁之间的冲突问题似乎从来没发生的,那这种锁是究竟是在什么样的场景下才会相互干上了。大家思考一个问题,数据库锁的设计是为谁服务的,或者说没有锁,会出现什么问题?

那就是事务并发中出现的脏读、幻读、不可重复读等违背预期结果的数据而产生严重的问题, 所以数据库的锁其实就是为了实现数据库隔离级别而设计的一种解决方案。

引用一下网上笔记对数据库事务隔离级别的阐述:

Read Uncommitted(读取未提交内容)

       在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。

Read Committed(读取提交内容)

       这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。

Repeatable Read(可重读)

       这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。

Serializable(可串行化) 

这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。

注意这里有一句话非常关键:简言之,它是在每个读的数据行上加上共享锁。所以只有隔离级别为 Serializable 时, 查询才会使用上共享锁, 看实例:

mysql> set tx_isolation=’Serializable’;

Query OK, 0 rows affected

mysql> set autocommit=0;

Query OK, 0 rows affected

mysql> SELECT  * FROM t_user_info;

打开另一个命令窗口,执行以下SQL:

mysql> update t_user_info set update_time = null ;

结果是:

mysql>  SELECT trx_id, trx_state,trx_mysql_thread_id, trx_isolation_level, t.trx_query FROM INFORMATION_SCHEMA.INNODB_TRX t ;

+——–+———–+———————+———————+—————————————————+

| trx_id | trx_state | trx_mysql_thread_id | trx_isolation_level | trx_query                                         |

+——–+———–+———————+———————+—————————————————+

| 2364   | LOCK WAIT |                  55 | REPEATABLE READ     | update t_user_info set update_time = null |

| 235B   | RUNNING   |                  32 | SERIALIZABLE        | NULL                                              |

+——–+———–+———————+———————+—————————————————+

2 rows in set

总之, 实际中的数据库基本上很少会把隔离级别设置为 Serializable(可串行化), 因为在这个级别,可能导致大量的超时现象和锁竞争。所以我们的火力还是集中放在排它锁之间的竞争问题上。

    原文作者:mysql
    原文地址: https://blog.csdn.net/ty8087/article/details/51108436
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞