锁对于传统数据库来说是非常重要的, 里面也掺杂各种权衡, 概念类较多, 本文只针对部分内容做了讲解.
单独给一行数据记录加锁, mysql 中 我们常用的 InnoDB 引擎支持行锁.
优势: 是常见关系型数据库中锁粒度最小的一种锁, 能够有效的提高并发操作.
劣势: 消耗更多资源, 用法不规范容易产生死锁
顾名思义, 对当前操作的整张表加锁,是目前 mysql 锁粒度最大的一种
最常见的 myisam 引擎只支持表锁.
InnoDB 引擎 sql 使用不当(如非主键、索引条件)会退化为表锁.
优势: 并发读没问题, 消耗资源少, 加减锁速度快, 可以避免死锁.
劣势: 大部分情况下DML操作并发低, 频繁对全表加锁, 发生锁冲突的概率非常高.
页锁是介于比表锁和行锁之间的一种锁, 只是对数据页进行锁定
页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但并发低,行级并发高,但耗资源。所以取了折衷的页级,一次锁定数据页中相邻的一组记录
mysql 中的BDB引擎支持页级锁
在 mysql 数据库中, 从全局看, 共分为这两种锁, 但他们的用处有很大区别.
latch 直译过来为“门闩(shuān)”, 类似于下图
它在数据库中的学名叫做 闩锁(一种轻量级的锁), 在 InnoDB 存储引擎中,latch又可以分为 mutex(互斥量)和 rwlock(读写锁), 其目的是用来保证并发线程操作临界资源的正确性,并且通常没有死锁检测的机制.
# 查看关于闩锁的统计信息
SHOW ENGINE INNODB MUTEX ;
+--------+------------------------+---------+
| Type | Name | Status |
+--------+------------------------+---------+
| InnoDB | rwlock: log0log.cc:838 | waits=1 |
+--------+------------------------+---------+
lock 锁, 是我们打交道最多的一种, 它主要针对的是事务,用来锁定的是数据库中的对象,如前面提到的表锁、页锁、行锁。并且一般lock的对象仅在事务 commit 或 rollback 后进行释放(不同事务隔离级别释放的时间可能不同)。此外,lock,正如在大多数数据库中一样,是有死锁机制的.
允许事物读取数据
当你拿到了共享锁, 你可以读取这条数据, 另一个人同样可以拿到共享锁去读取数据(这里我们称为锁兼容)
但是同时另一个人也来了, 他同时想要拿排他锁去更新此条数据,那么他必须等待释放共享锁才可以拿到排他锁更新数据(这里我们称之为锁不兼容)
允许事物删除或更新一行数据
前面我们举例了 如果别人已经持有共享锁了, 其他人是不能拥有排他锁的.
当一个人拿到此条数据的排他锁, 不能同时再拿到排他锁和共享锁的(锁不兼容).
排他锁(X Lock)和任何锁都不兼容.
我们知道 当我们频繁 更新数据 加排他锁 (X Lock) 的时候, 因为其锁的不兼容性, 会严重影响正常的数据查询性能.
一致性的非锁定读 是指 InnoDB 存储引擎通过行多版本控制(multi versioning)的方式来读取当前执行时间数据库中行的数据。如果读取的行正在执行 DELETE 或 UPDATE 操作,这时读取操作不会因此去等待行上锁的释放。相反地,InnoDB 存储引擎会去读取行的一个快照数据
一致性非锁定读的快照数据其实是读的 undo 数据(undo是用来回滚事务中的数据), 没有额外的锁操作, 所以读取速度非常的快.
一致性的非锁定读因为不需要等待排他锁 (X Lock) 的释放, 所以极大的提高了并发的性能, 在 innodb 事务隔离级别 不可重复读(read-committed)、可重复读(repeatable-read 默认级别) select 使用的是 一致性的非锁定读.
思考: 一致性的非锁定读 解决了什么问题? 带来了什么问题?
在一些强一致的场景, 我们是希望让用户读取到的永远是最新的数据. 这时候, 我们需要使用 一致性锁定读 的场景.
下面展示了两种基于数据库查询语句上X锁和S锁, 一般与显示的事务组合使用.
SELECT…FOR UPDATE对读取的行记录加一个X锁,其他事务不能对已锁定的行加上任何锁 SELECT…LOCK IN SHARE MODE对读取的行记录加一个S锁,其他事务可以向被锁定的行加S锁,但是如果加X锁,则会被阻塞
FOR UPDATE 也是我们经常所说的悲观锁, 对应的还有乐观锁, 乐观锁更多是业务层面的实现, 这里不再讲述.
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去.
目前主要还是靠业务逻辑来解决, 如果程序是串行, 就不会产生死锁, 只有并发情况下才有可能产生, 程序并行+数据库并行(行级锁)运行, 可能会发生死锁.
这里可以使用此命令查看所有线程, 对应的提示直接 kill 掉即可
show processlist
查看目前锁的情况
1:查看当前的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;
2:查看当前锁定的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
3:查看当前等锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;
当然你也可以通过配置 innodb_lock_wait_timeout 属性,来指定锁的超时时间, 超时数据库系统自动 kill.
不过如果出现了长时间获取不到锁, 数据库会自动进行死锁检测, 并进行终止.
当然,保证业务中操作数据库的执行顺序, 避免交叉执行, 基本能够避免的死锁情况。
本文一部分参考概念 《mysql 技术内幕》一书.