数据库——MySQL之锁


数据库—MySQL之锁


brycezou@163.com


        在深入阅读《MySQL技术内幕》的基础上,结合实践整理得到本文,希望对大家有所帮助。

1、锁概述

  • 开发多用户OLTP应用程序的难点:

    • 最大程度地利用数据库进行并发访问
    • 确保每个用户能以一致的方式读取和修改数据
  • 锁用于协调对共享资源的访问。锁是数据库系统区别于文件系统的一个关键特性,数据库系统使用锁是为了支持对共享资源的并发访问、提供数据的完整性和一致性。

  • 不同数据库中锁机制的实现方式大不相同。在Microsoft SQL Server中,锁是一种稀有的资源,锁越多,开销就越大,因此会有锁升级,行锁升级到表锁。而InnoDB存储引擎提供了一致性非锁定读、行级锁支持,且行级锁没有相关的开销,可以同时得到并发性和一致性。

  • InnoDB存储引擎实现了两种标准的行级锁:共享锁 S Lock,允许事务读一行数据;排它锁 X Lock,允许事务删除或更新一行数据。当一个事务已经获得了某一行的共享锁时,那么其它事务可以立即同时获得该行的共享锁;但如果有事务想获得该行的排它锁,则必须等待所有事务释放该行上的共享锁。

2、查询锁和事务的状态

  • 可以通过 information_schema 库下的 INNODB_LOCK_WAITS、INNODB_LOCKS和INNODB_TRX 这三张表来监控当前的事务和锁状态,下面给出一个例子。

  • 这是第1个mysql客户端:开始一个事务,但不提交

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> update t set b='888' where a=5;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1  Changed: 0  Warnings: 0
  • 这是第2个mysql客户端:开始一个共享模式的查询,该语句一直处于等待状态,直到超时
mysql> select * from t where a=5 lock in share mode;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
  • 在上面的查询超时前,在第3个mysql客户端中查询当前锁和事务的状态(结果只保留了部分列)
mysql> use information_schema;
Database changed

mysql> select * from INNODB_TRX;
+--------+-----------+-----------------------+----------------------------------------------+
| trx_id | trx_state | trx_requested_lock_id | trx_query                                    |
+--------+-----------+-----------------------+----------------------------------------------+
| 35610  | LOCK WAIT | 35610:103:3:13        | select * from t where a=5 lock in share mode |
| 35609  | RUNNING   | NULL                  | NULL                                         |
+--------+-----------+-----------------------+----------------------------------------------+
2 rows in set (0.01 sec)

mysql> select * from INNODB_LOCKS;
+----------------+-------------+-----------+-----------+------------+------------+-----------+
| lock_id        | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_data |
+----------------+-------------+-----------+-----------+------------+------------+-----------+
| 35610:103:3:13 | 35610       | S         | RECORD    | `test`.`t` | PRIMARY    | 5         |
| 35609:103:3:13 | 35609       | X         | RECORD    | `test`.`t` | PRIMARY    | 5         |
+----------------+-------------+-----------+-----------+------------+------------+-----------+
2 rows in set (0.00 sec)

mysql> select * from INNODB_LOCK_WAITS;
+-------------------+-------------------+-----------------+------------------+
| requesting_trx_id | requested_lock_id | blocking_trx_id | blocking_lock_id |
+-------------------+-------------------+-----------------+------------------+
| 35610             | 35610:103:3:13    | 35609           | 35609:103:3:13   |
+-------------------+-------------------+-----------------+------------------+
1 row in set (0.01 sec)
  • 上述结果中主要字段的含义为
在 INNODB_LOCK_WAITS 中,
    requesting_trx_id:申请锁资源的事务ID 
    requested_lock_id:申请的锁的ID
    blocking_trx_id:阻塞的事务ID 
    blocking_lock_id:阻塞的锁的ID 
在 INNODB_LOCKS 中,
    lock_id:锁的ID 
    lock_trx_id:事务ID 
    lock_mode:锁的模式,共享锁或排它锁 
    lock_type:锁的类型,表锁或行锁 
    lock_table:要加锁的表 
    lock_index:锁的索引
    lock_data:被锁住的行的主键值。当是表锁时,该值为NULL。该值不完全可信 
在 INNODB_TRX 中,
    trx_id:InnoDB存储引擎内部唯一的事务ID 
    trx_state:当前的事务状态 
    trx_requested_lock_id:等待事务的锁ID 
    trx_query:事务运行的SQL语句 
    trx_weight:事务的权重,反映了一个事务修改和锁住的行数。在InnoDB存储引擎中,当发生死锁要回滚时,会选择该值最小的进行回滚

        显然,事务 35609 目前正在运行,而事务 35610 正处于 LOCK_WAIT 状态,运行的SQL语句是 select * from t where a=5 lock in share mode。详细过程可理解为:

        首先,在事务 35609 中,由于要更新记录,因此,对主键值 a=5 的记录加上了排它锁 X ,锁 ID 为 35609:103:3:13。

        其次,在事务 35610 中,由于要以共享模式查询,因此向表中主键值 a=5 的记录申请了一个共享锁 S。但由于该记录已经在事务 35609 中获得了一个锁 X ,且事务没有提交,锁 X 还没有被释放,所以事务 35610 只能继续等待锁。

3、一致性非锁定读

  • InnoDB存储引擎通过行的多版本控制来实现一致性的非锁定行读(consistent nonblocking read)。如果要读取的行被加上了排它锁 X(正在执行UPDATE、DELETE操作),这时并不会等待锁被释放,而是会读取行的一个快照数据。

  • 快照数据是指当前行的历史版本数据,它是通过Undo段来实现的,而Undo段用来在事务中回滚数据,因此快照数据本身没有额外的开销。此外,快照数据是不会被上锁的,因为没有必要修改历史数据。

  • 一个行可能有多个历史版本,即有多份快照数据。

  • 非锁定读的机制大大提高了数据读取的并发性,这是InnoDB默认的读取方式。但并不是每个事务隔离级别下都是一致性读,而且,就算都是一致性读,快照数据的定义也不同:

    • 在Read Committed隔离级别下,读取被锁定行的最新一份快照数据。
    • 在Repeatable Read(InnoDB默认)隔离级别下,读取被锁定行在事务开始时的快照数据。
  • 下面是两个时序有重合的事务,用来模拟并发情况。在2种不同的事务隔离级别下,给出 Session A 的查询结果

    Time LineSession ASession BRepeatable ReadRead Committed







    begin;
    select * from t where a=5;
    555555
    begin;
    update t set b=8888 where a=5;
    select * from t where a=5;555555
    commit;
    select * from t where a=5;
    commit;
    5558888

  • 默认情况下,InnoDB存储引擎的 select 操作使用一致性非锁定读。但是在某些情况下,我们需要对读取操作进行加锁。InnoDB支持如下两种加锁操作,它们必须存在于事务中,当事务提交时,锁也就释放了。使用这两种操作时必须使用begin、start transaction或set autocommit=0。

    • select … for update:对读取的行记录加 X 锁,其它事务想在此行上加任何锁都会被阻塞。
    • select … lock in share mode:对读取的行记录加 S 锁,其它事务可以向被锁定的记录加 S 锁,但 X 锁会被阻塞。

4、锁的算法


        在 Repeatable Read 隔离级别,InnoDB存储引擎对于行的锁定默认使用共享的或者排它的 Next-Key Lock 算法: 锁定一个范围,并且锁定记录本身。下面的例子中,查询锁定的数值区间是 (,6] ,因此,更新3和6都会被阻塞,而更新8则会成功。其它两种行锁算法分别是 Record Lock 和 Gap Lock。




























Time LineSession ASession B










begin;
select * from t where a < 6 lock in share mode;

begin;
update t set b=333 where a=3;
ERROR 1205 (HY000):Lock wait timeout exceeded;

update t set b=666 where a=6;
ERROR 1205 (HY000):Lock wait timeout exceeded;

update t set b=888 where a=8;
Query OK, 1 row affected (0.00 sec)
commit;
commit;

5、锁的问题


        通过锁可以实现事务的隔离性要求,使事务可以并发地工作,但锁同样会带来问题。受事务隔离性的限制,锁只会带来3种问题:丢失更新、脏读、不可重复读。如果可以避免这3种情况,将不会产生并发异常。

  • 丢失更新:要避免丢失更新,可以使用排它锁将并发事务改为串行事务。
  • 脏读:读到了脏数据,即一个事务可以读到另一个事务中未提交的数据,这显然违反了事务的隔离性原则。脏数据是指,在缓冲池中已被修改但还未提交的数据。脏读现象在生产环境中并不常发生,因为脏读的必要条件是隔离级别设置为READ UNCOMMITED,而目前主流数据库其默认的事务隔离级别至少都设置为READ COMMITED:InnoDB是READ REPEATABLE,Microsoft SQL Server和Oracle都是READ COMMITED。
  • 不可重复读:在一个事务内多次读同一数据,读到的结果不相同。和脏读不同的是,不可重复读,读到的确实是已经提交的数据,但其违反了事务的一致性要求。在隔离级别
    READ COMMITED下容易出现不可重复读,但一般来说,不可重复读是可以接受的。InnoDB存储引擎默认的事务隔离级别是READ REPEATABLE,避免了不可重复读。

6、阻塞


        当一个资源已被一个事务占有时,另一个事务执行mutex_enter函数会发生等待,这就是阻塞。阻塞可以保证事务并发且正常地运行。 默认情况下,InnoDB存储引擎不会回滚超时引发的错误异常(默认值innodb_rollback_on_timeout=OFF),参数innodb_lock_wait_timeout用来设置等待时间,默认为50秒。 当事务发生超时异常时,既没有commit,也没有rollback,这是十分危险的,用户必须进行善后处理。

7、死锁


        如果程序是串行的,则不可能发生死锁。死锁只发生于并发情况,死锁的根本原因是互相等待对方的资源。InnoDB存储引擎有一个后台的锁监控线程,可以自动侦测到大多数死锁,并且在检测到死锁后,会马上回滚一个事务,不需要人为干预。 InnoDB会自动为外键添加索引,避免由此产生的死锁。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值