热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

数据库事务(二)Mysql数据库锁

我们已经了解了数据库事务隔离级别脏读,虚读,不可重复读,不过想要更好的对数据库操作,提高数据库读写的性能,我们还需要了解数据库的另一个机制---数据库锁!其实,刚刚使用mysql数

我们已经了解了数据库事务隔离级别脏读,虚读,不可重复读,不过想要更好的对数据库操作,提高数据库读写的性能,我们还需要了解数据库的另一个机制---数据库锁!

其实,刚刚使用mysql数据库不久的伙伴,对数据库锁可能没啥概念,而且可能还有疑问,难道数据库会死锁?没见过啊,死锁了数据库不就GG了么?首先要确认一个东西,那就是数据库当然会死锁,既然有事务,有隔离,有并发,逻辑一旦处理不好,死锁肯定是会发生的。


至于大多数小伙伴没遇到过,一个是因为你可能接触的业务并发不是特别高,业务逻辑不是特别复杂,还有一个重要的原因就是数据库内部有一个监测死锁的机制,一旦发现数据库死锁,就会触发回退操作,不会让数据库真的宕机。

下面我们来认识下数据库的锁,既然是数据隔离,高并发情况下操作数据,肯定是要加锁的,要不然就乱套了。等我们认识了锁,那死锁机制对我们来说就很简单了,而且,可以轻易的写出一个死锁的业务操作,就像使用java代码实现死锁一样,嘿嘿。

我们知道java中的锁(常见的)一般是乐观锁(CAS),继承AQS实现的锁通过阻塞对垒实现的锁,还有就是Synchronized的监视器实现的锁。那么MySQL呢?java是一种语言,主要针对的是数据逻辑或者说运行程序需要的资源进行加锁,锁的主要是资源,而MySQL数据库呢?这是一个关系型数据库,其中的锁主要是对数据的锁定,防止出现脏读等事件,所以两者的锁肯定是有差别的。

我们来看下MySQL的锁,主要是行锁(Record Locks),间隙锁(临键锁),意向锁(Insert Intention Locks),自增锁(Auto-inc Locks)等,下面我们来展开说下。

1.行锁

我们先建立一个数据表,这样看着操作说,更清楚一些。下面是建表和几条数据的sql语句。

CREATE TABLE `student` (
`id` int(10) NOT NULL AUTO_INCREMENT COMMENT 'id主键',
`age` int(11) NULL DEFAULT NULL COMMENT '年龄',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '名称',
`id_card` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '身份证号',
`stage` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '等级',
`create_date` datetime(0) NULL DEFAULT NULL COMMENT '创建日期',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `idx_card`(`id_card`) USING BTREE COMMENT '唯一索引--身份编号',
INDEX `idx_stage`(`stage`) USING BTREE COMMENT '普通索引--等级索引'
) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '个人测试索引-学生表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES (1, 16, '张三', '110120200402023456', '9', '2020-10-21 14:07:15');
INSERT INTO `student` VALUES (2, 16, '张三加', '110120200402023454', '9', '2020-10-21 14:07:15');
INSERT INTO `student` VALUES (3, 16, '张四', '110120200402023457', '8', '2020-10-21 14:07:15');
INSERT INTO `student` VALUES (4, 16, '张五', '110120200402023458', '7', '2020-10-21 14:07:15');
INSERT INTO `student` VALUES (5, 16, '张六', '110120200402023459', '6', '2020-10-21 14:07:15');
INSERT INTO `student` VALUES (6, 16, '张七', '110120200402023460', '5', '2020-10-21 14:07:15');
INSERT INTO `student` VALUES (7, 16, '张八', '110120200402023465', '4', '2020-10-21 14:07:15');
INSERT INTO `student` VALUES (8, 16, '张九', '110120200402023466', '15', '2020-10-21 14:07:15');
INSERT INTO `student` VALUES (9, 16, '张氏', '110120200402023467', '16', '2020-10-21 14:07:15');
INSERT INTO `student` VALUES (10, 16, '李四', '110120200402023480', '18', '2020-10-21 14:07:15');
INSERT INTO `student` VALUES (11, 16, '李五', '110120200402023481', '19', '2020-10-21 14:07:15');
INSERT INTO `student` VALUES (12, 16, '李六', '110120200402023482', '20', '2020-10-21 14:07:15');

截个图吧,看着清楚些哈~





mysql student数据表,唯一索引 id_card​​,普通索引stage



进行讲解锁之前我们先了解接隔离级别的查看,就是下面这个语句:

select @@tx_isolation as 当前隔离级别 ,@@global.tx_isolation as 全局隔离级别; 
事务的隔离级别对锁是有影响的哦,在进行锁操作之前,建议大家最好查看下事务的隔离级别,当然MySQL默认的就是REPEATABLE-READ

好了,我们来看下这几条sql语句 。

-- 开启事务A
START TRANSACTION;
-- id=7的数据
SELECT * FROM student where id=7 for UPDATE;
-- --上半部分
-- ----------
-- --下半部分
ROLLBACK;
-- ----最好在可视化工具的两个查询页面,方便看结果
-- 启动事务B
START TRANSACTION ;
-- id=7的数据
SELECT * FROM student where id=7 for UPDATE;
-- --上半部分
-- ----------
-- --下半部分
ROLLBACK;

       我们知道查询语句后面加上for update,就是对整个student表加入了意向锁(下边说),然后对id=7的记录加上了独占锁,锁了住查询的结果数据,其实很明显,for update(为了修改)你都想修改了,肯定不允许其他人修改了,不然一起修改还不乱了套。

我们运行事务A 的上半部分,就是锁住了id=7的这条数据,事务A未结束。然后启动事务B,再次去查询李四(for update),我们会发现无法查询到李四的数据,事务一直在运行,也没有报错信息的,其实就是在等待,等待事务A结束(提交或者回滚)。当然如果事务B去查询id=8的数据肯定是没有问题的

其实这个时候对 id=7 这条数据加锁加的就是行锁。

我们来看下行锁的官方描述A record lock is a lock on an index record(行锁一定是作用在索引上的)。       

student表的id(建表的时候就加了聚簇索引),idx_card,idx_stage都是是加了索引的,如果以这些条件进行加锁操作,是会锁住这一条数据的。

行锁分为两种:


  • 共享锁(S) 共享锁也称为读锁,读锁允许多个连接可以同一时刻并发的读取同一资源,互不干扰;


  • 排他锁(X) 排他锁也称为写锁,一个写锁会阻塞其他的写锁或读锁,保证同一时刻只有一个连接可以写入数据,同时防止其他用户对这个数据的读写。


让然如果条件是name这种没有加索引的字段,想要锁住一条数据的话,那只能使用表锁了,这样的话就比较消耗资源,而且容易发生死锁的。

2.间隙锁 

我们刚刚看了以索引条件进行查询,会进行行锁那么,什么是间隙锁呢?我们来看下下面几条语句。

-- 开启事务A
START TRANSACTION;
-- id_card大于'110120200402023465',小于'110120200402023481'的数据
SELECT * FROM student where id_card>'110120200402023465' and id_card <'110120200402023481' for UPDATE
-- --上半部分
-- ----------
-- --下半部分
ROLLBACK;
-- -----------------------------------------------------------
-- 启动事务B
START TRANSACTION ;
-- 删除 id_card='110120200402023481' 的数据
DELETE FROM student where id_card='110120200402023481';
-- --上半部分
-- ----------
-- --下半部分
ROLLBACK;

运行事务A的上半部分,锁住了id_card大于'110120200402023465',小于'110120200402023481'的数据,这个时候id_card大于'110120200402023465',小于'110120200402023481',这个数据段是被锁住的,如果插入id_card=110120200402023469的数据,肯定是失败的。

但是如果运行事务B的上半部分呢?id_card小于'110120200402023481',是不是'110120200402023481'没有被锁住呢?当然不是,大家执行下SQL语句,会发现事务B一直在等待,直到事务A  rollback 之后,事务B才能够继续运行,很明显小id_card数据为'110120200402023481'的这条数据被锁住了。

查询相关概念,我们会发现,间隙锁锁住的策略是左开右闭,当你锁定一个区间时,左侧的数据没有被锁住,右侧的数据是被锁住的。所以事务A锁住的区间是('110120200402023465','110120200402023481'],大家可以试一下,事务B如果操作id_card=110120200402023465这条数据的话,是没有被锁住的。

有了间隙锁,锁住了某一段,这样进行一些控制,其实就能避免幻读的发生,同时有进行了最小范围的锁定,保证了数据库性能。

当然间隙锁针对的也是索引,没有索引的话也是直接锁表的。

3.意向锁

为了支持多粒度锁机制(multiple granularity locking),即允许行级锁与表级锁共存,而引入了意向锁(intention locks)。意向锁是指,未来的某个时刻,事务可能要加共享/排它锁了,先提前声明一个意向。

SELECT * FROM student where id_card>'110120200402023465' and id_card <'110120200402023481' for UPDATE

这个其实就是一个意向锁。


  1. 意向锁是一个表级别的锁(table-level locking);

  2. 意向锁又分为:

    • 意向共享锁(intention shared lock, IS),它预示着,事务有意向对表中的某些行加共享S锁

    • 意向排它锁(intention exclusive lock, IX),它预示着,事务有意向对表中的某些行加排它X锁


 语法:

select ... lock in share mode;  要设置IS锁;
       select ... for update;       要设置IX锁;

下面是意向锁与S锁与X锁的互斥关系 

 

 至于自增锁,我想大家都用过,每次建表的时候我们的id一般都是设置成自增的,这里其实就加了一个自增锁。

好了,数据库锁就简单的说到这里啦。

No sacrifice,no victory~


推荐阅读
  • MySQL Decimal 类型的最大值解析及其在数据处理中的应用艺术
    在关系型数据库中,表的设计与SQL语句的编写对性能的影响至关重要,甚至可占到90%以上。本文将重点探讨MySQL中Decimal类型的最大值及其在数据处理中的应用技巧,通过实例分析和优化建议,帮助读者深入理解并掌握这一重要知识点。 ... [详细]
  • 数据类型和操作数据表2.1MySQL类型之整型2.2MySQL数据类型之浮点型2.3日期时间型DATE1支持时间:1000年1月1日~9999年12月31日DATETIME ... [详细]
  • 本文详细介绍了在 Oracle 数据库中使用 MyBatis 实现增删改查操作的方法。针对查询操作,文章解释了如何通过创建字段映射来处理数据库字段风格与 Java 对象之间的差异,确保查询结果能够正确映射到持久层对象。此外,还探讨了插入、更新和删除操作的具体实现及其最佳实践,帮助开发者高效地管理和操作 Oracle 数据库中的数据。 ... [详细]
  • php更新数据库字段的函数是,php更新数据库字段的函数是 ... [详细]
  • MySQL 5.7 学习指南:SQLyog 中的主键、列属性和数据类型
    本文介绍了 MySQL 5.7 中主键(Primary Key)和自增(Auto-Increment)的概念,以及如何在 SQLyog 中设置这些属性。同时,还探讨了数据类型的分类和选择,以及列属性的设置方法。 ... [详细]
  • 本文详细介绍了MySQL数据库的基础语法与核心操作,涵盖从基础概念到具体应用的多个方面。首先,文章从基础知识入手,逐步深入到创建和修改数据表的操作。接着,详细讲解了如何进行数据的插入、更新与删除。在查询部分,不仅介绍了DISTINCT和LIMIT的使用方法,还探讨了排序、过滤和通配符的应用。此外,文章还涵盖了计算字段以及多种函数的使用,包括文本处理、日期和时间处理及数值处理等。通过这些内容,读者可以全面掌握MySQL数据库的核心操作技巧。 ... [详细]
  • DAO(Data Access Object)模式是一种用于抽象和封装所有对数据库或其他持久化机制访问的方法,它通过提供一个统一的接口来隐藏底层数据访问的复杂性。 ... [详细]
  • importpymysql#一、直接连接mysql数据库'''coonpymysql.connect(host'192.168.*.*',u ... [详细]
  • 本文介绍如何使用 Python 的 DOM 和 SAX 方法解析 XML 文件,并通过示例展示了如何动态创建数据库表和处理大量数据的实时插入。 ... [详细]
  • 如何在Java中使用DButils类
    这期内容当中小编将会给大家带来有关如何在Java中使用DButils类,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。D ... [详细]
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • 深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案
    深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案 ... [详细]
  • IOS Run loop详解
    为什么80%的码农都做不了架构师?转自http:blog.csdn.netztp800201articledetails9240913感谢作者分享Objecti ... [详细]
  • 本文介绍了在 MySQL 中如何使用正则表达式来提高查询效率,通过具体示例展示了如何筛选包含中文字符的记录,并详细解释了正则表达式的各种特殊字符和结构。 ... [详细]
  • 在什么情况下MySQL的可重复读隔离级别会导致幻读现象? ... [详细]
author-avatar
962326154_5af7cb
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有