MYSQL:为什么要学习数据库锁
很多人在开发过程中很少注意到这些锁问题,也很少对程序加锁(除了对定量精度要求极高的库存这种情况)
一般听说广为人知的乐观锁和悲观的锁。一旦我们理解了基本含义,就没有了~~~
保证:即使我们没有关于锁定的这些知识,我们的程序通常也能运行。我还能跑得很好。因为数据库隐式地为我们添加了这些锁:
- 对于 UPDATE、DELETE、INSERT 语句,InnoDB 会自动为包含的数据集添加排它锁(X);
- MyISAM 在执行 SELECT 查询语句之前会自动为包含的数据集添加排它锁(X)。读锁被添加到所有包含的表中。在执行更新操作(UPDATE、DELETE、INSERT 等)之前,写锁将自动添加到包含的表中。这个过程不需要用户干预;
只会出现在一些需要下才手动锁定的特殊场景中。学习数据库锁知识的目的是:
- 让其在特殊场景下发挥作用
- 更好地控制我们编写的程序
- 与他人讨论数据库技术 有时你可以添加几句话
- 建立自己的知识基础系统!采访中确实如此
2。办公桌锁简介
首先,根据锁的碎片化,可以分为两大类:
- 台式锁负载低,添加快;不会出现僵局;锁强度强,锁冲突概率高,并发度最低;
- 行锁开销很大,加锁速度慢;发生交通堵塞;锁粒度小,锁冲突概率小,并发性高;
不同。存储引擎支持不同的锁定粒度:
- InnoDB 支持行锁和表锁!
- MyISAM 仅支持表锁!
InnoDB 仅使用行级锁通过索引条件检索数据。否则,InnoDB将使用表锁
- 也就是说,InnoDB行锁是基于索引的!
表锁分为两种方式:
- 表读锁(Table read lock)
- 表写锁(Table write lock)
- 如下图可以清楚的看到,在Table read lock 环境中表写锁:读和读不阻塞、读和写阻塞、写和写阻塞!
- 读取不阻塞:当前用户正在读取数据,其他用户也在读取数据,不会被锁定。
- 读写被阻塞:当前用户正在读取数据,其他用户无法修改当前用户正在读取的数据,将被锁定。 !
- 写写阻塞:当前用户修改数据,其他用户无法修改当前用户修改的数据,会被锁定!
从上面可以看出:读和写是互斥的,读和写是顺序的。
- 如果一个进程想要获取读锁,而另一个进程想要获取写锁。在mysql中,写锁优先于读锁!
- 写锁难度和读锁优先级可以通过 max_write_lock_count 和 low-priority-updates 参数进行调整
值得注意的是:
- MyISAM 可以支持并发查询和插入操作。您可以使用并发_插入系统变量来指定哪种模式。 MyISAM中的默认设置是:如果MyISAM表中没有空洞(即表中间没有被删除的行),MyISAM允许一个进程读取该表,而另一个进程从该表中读取。将记录插入到表的末尾。
- 但是InnoDB存储引擎不支持这个!
3。乐观锁和悲观括号
无论是专注阅读的程度,还是可重复阅读的隔离,都是为了解决阅读和写作的争用问题。
我们简单从可重复读隔离层面来考虑问题:
此时用户李四的操作丢失:
- 丢失更新:一个事务的更新覆盖了其他事务的更新结果。
(ps:我想不出更好的例子来说明丢失更新的问题。上面的例子虽然也是丢失更新,但在一定程度上是可以接受的。不知道有没有人认为丢失是不可接受的更新。这是一个例子...)
解决方案:
- 使用 Serialized 隔离级别,事务是顺序执行的!
- 乐观锁
- 悲观锁
- 乐观锁是一个想法。一个特殊的实现是表中有一个版本字段。第一次读取时,将检索该字段。处理完业务逻辑并开始更新后,需要再次检查该字段的值是否与第一次相同。如果按照同样的方式更新,否则会被拒绝。之所以称为乐观,是因为这种模式不会锁定数据库,会等到更新时看是否可以更新。
- 悲观锁是数据库级别的锁,会阻塞挂起的锁。
3.1。悲观锁
所以,按照上面的例子。我们使用悲观锁其实很简单(手动添加行锁即可):
- select * from xxxx to update
在select语句后面添加for update相当于添加了排它锁(写锁)。一旦获得写锁,其他事务就无法更改它!必须要等当前事务发生变化才可以更改。
- 也就是说,如果张三使用select...来更新,李四就无法更改记录了~
3.2 , 乐观锁
乐观锁 这不是数据库级别的锁,这是必须手动添加的锁。一般来说,我们添加一个版本字段来实现:
具体流程如下:
张三 from table select * --->会进行记录查询,版本字段也会有
李四 select * from the table --->该记录会被查询,还会有版本字段
如果李四改变了这条记录: update A set Name=lisi,version=version+1 where ID=#{id},version=#{version},将之前查询到的版本与当前数据的版本进行比较,并更新版本字段
目前数据库记录如下:
张三也变了这条记录:更新A集Name=lisi,version=version+1 where ID=#{id} and version=#{version}但是失败!因为当前数据库中的版本与请求的版本不兼容!
4。 GAP间隙锁定
当我们使用范围条件而不是相等条件检索数据并请求共享锁或独占锁时,InnoDB将锁定满足范围条件的现有数据记录的索引条目;键值在条件范围内但不存在的记录称为“GAP”。 InnoDB 也将缩小这个“差距”。该锁定机构是所谓的间隙锁定。
值得注意的是,间隙锁只有在可重复读隔离级别下才会使用~
示例:如果emp表中只有101条记录,empid值为1,2,..., 100,101
Select * from emp where empid > 100 进行更新;
上面是范围查询。 InnoDB不仅会锁定empid值为101且符合条件的记录,还会锁定empid大于101的记录(那些记录不存在),即“间隙”锁。
InnoDB 使用间隙锁有两个目的:
- 防止幻读(如上所述,可以通过将 GAP 锁移至重复读隔离级别以下来避免幻读)
- 满足恢复要求和复制 MySQL 恢复引擎要求是强制性的:在事务提交之前,其他并发事务不能插入任何符合锁条件的记录,这意味着不允许幻读
5、死锁
并发问题是必不可少的。 MySQL中也存在锁、死锁的问题。
但是总的来说,MySQL通过回滚帮助我们解决了很多死锁问题,但是死锁是无法完全避免的。您可以参考以下经验参考,尽可能避免死锁:
- 1)按特定顺序访问表和行。例如,当两个任务批量更新时,一个简单的做法是先对ID列表进行排序,然后执行,避免交叉等待锁;通过调整两个事务的SQL顺序一致,还可以避免死锁。
- 2)把大事分成小事。大事务更容易出现死锁。如果公司允许,将大额交易分成小额交易。
- 3)在同一个事务中,尽量同时锁定所有必要的资源,以减少死锁的概率。
- 4) 降低绝缘水平。如果业务允许,降低隔热等级也是一个不错的选择。例如,将隔离级别从 RR 调整为 RC 可以避免由于间隙闭合而导致的许多拥塞。
- 5) 在表中添加合理的索引。我们可以看到,如果不使用索引,就会对表的每一行加一把锁,死锁概率会大大增加。
6。锁定总结
上面提到了很多关于MySQL数据库锁定的内容,现在我们简单总结一下。
其实程序员很少关心表锁:
- 在MyISAM存储驱动中,执行SQL语句时会自动添加。
- 如果InnoDB存储引擎没有使用索引,会自动加表锁。
我们大多数使用MySQL的人现在都使用InnoDB。 InnoDB 支持行锁:
- 共享锁--读锁--S 锁
- 独占锁--写锁--X 锁
V 默认情况下,select 不会添加任何行锁。事务可以通过以下语句向记录集添加共享锁或排它锁。
- 共享密钥:SELECT * FROM table_name WHERE ... END IN SHARED MODE。
- 独占锁 (X):SELECT * FROM table_name WHERE ... TO UPDATE。
InnoDB还实现了基于行锁的多版本MVCC并发控制。 MVCC 在隔离级别下工作在已提交读和可重复读下。 MVCC可以实现非阻塞读写!
InnoDB 的可重复读隔离级别和 GAP 锁定避免了幻读!
- 乐观锁其实就是一个思想,顾名思义:不加锁就更新数据,出了问题就不更新(返回)。这通常是通过向数据库添加版本字段来完成的。
- 悲观锁定使用数据库行锁定。假设数据库会出现并发冲突,所以直接锁数据。在当前交易
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。