当前位置: 首页 > 科技观察

MySQL中的表级锁不好吗?

时间:2023-03-12 12:51:39 科技观察

当然不是!其实今天想和大家聊聊MyISAM引擎,但是在写的过程中发现锁的话题可以单独写,所以才有了今天的文章。说起MyISAM和InnoDB的区别,很多人都知道区别就是一个是表锁,一个是行锁,那么大家有没有想过表锁和行锁的区别呢?兄弟,我是来和你聊聊这个话题的。1.锁先说MySQL中的锁。当多个事务或者多个进程访问同一个资源时,为了保证数据的一致性,需要使用MySQL的锁机制。从锁定资源的角度来看,MySQL中的锁大致可以分为三种:表级锁定:表级锁定的特点是开销小,加锁速度快,不会出现死锁。但是加锁粒度大,锁冲突概率高,并发度低。行级锁:行级锁的特点是开销大,加锁速度慢,还可能出现死锁。但其加锁粒度小,锁冲突概率低,并发度高。页锁:开销和加锁时间介于表锁和行锁之间,会产生死锁。锁粒度介于表锁和行锁之间,并发度一般。虽然理论上有三种锁,但是对于在座的所有朋友,包括宋哥来说,前两种是我们在日常开发中接触最多的,即表级锁和行级锁。在MySQL中,MyISAM引擎是表级锁,而InnoDB引擎支持行级锁。不过需要注意的是,InnoDB也支持表级锁,但默认是行级锁。2.表级锁MySQL的表级锁有两种模式:表共享读锁(TableReadLock)。表独占写锁(TableWriteLock)。MyISAM引擎会在执行select时自动给相关表加读锁,在执行update、delete、insert时自动给相关表加写锁。2.1表共享读锁我们先来看一下表共享读锁。有共享读锁的表不会阻塞其他会话的读请求,但会阻塞其他会话的写请求。让我们来演示一下这个效果。在下面的例子中,我们将准备两个代表两个会话的窗口。首先,我们创建一个新表并选择MyISAM作为存储引擎。DDL如下:CREATETABLE`user`(`id`int(11)unsignedNOTNULLAUTO_INCREMENT,`name`varchar(255)COLLATEutf8mb4_unicode_ciDEFAULTNULL,PRIMARYKEY(`id`))ENGINE=MyISAMDEFAULTCHARSET=utf8mb4COLLATE=utf8mb4_unicode_ci;添加一条测试数据:insertintouser(name)values('javaboy');然后我们分别在两个命令行窗口登录mysql,模拟两个会话。先给第一个窗口的表加读锁,如下:locktableuserread;然后在第二个窗口读取数据:select*fromuser;可以发现可以正常读取。然后我们尝试在第二个窗口写入一条数据:insertintouser(name)values('itboyhub');这条写语句会卡住,如下:卡住的原因是因为用户表当前被共享读锁锁住了,这时候我们需要去第一个窗口解锁表。这时候第二个窗口的insertsql就可以执行了。如下:unlocktables;当这条SQL执行完后,第二个窗口的insert语句会立即执行。下面是窗口2的截图:可以看到有共享读锁的表不会阻塞其他会话的读(select)请求,但是会阻塞其他会话的写(insert、update、delete)请求。需要注意的是,同一个SQL如果同一个表名出N次,则该表会被加锁N次,如下:思路:我们在window1中对user表加了锁,那么在window1中是是否可以对用户表执行插入/更新/删除等写操作?在评论区秀出你的答案吧~2.2表的排他写锁这个排他写锁就是大家熟知的排他锁,它会阻止其他进程访问同一张表,其他进程的读写操作只会被当前锁释放后执行。让我们演示一下这个过程。仍然有两个窗口。首先我们在第一个窗口执行锁表操作:locktableuserwrite;然后到第二个窗口做查询操作,如下:可以看到因为是排它锁,所以查询操作也被阻塞了。此时需要在窗口1中解锁表的锁,继续执行窗口2中的查询操作。这个就是表排他写锁,也就是排它锁。在MyISAM存储引擎中,SELECT语句自动加共享锁,update/delete/insert操作加排他锁。2.3concurrent_insert我们讲了表级锁的两种基本模式。在具体的使用过程中,我们还可以通过concurrent_insert配置一些并发行为。concurrent_insert有三个不同的值:NEVER:加读锁后,不允许其他会话并发插入。AUTO:加读锁后,如果表中没有数据被删除,其他session可以并发插入。ALWAYS:加读锁后,允许其他会话并发插入。需要注意的是,在MySQL5.5.3之前,NEVER、AUTO、ALWAYS分别被替换为0、1、2。通过showglobalvariableslike'%concurrent_insert%'命令,我们可以查看当前数据库中concurrent_insert的值,如下:可以看到,数据库中concurrent_insert的默认值为AUTO。有的朋友可能会说,什么?汽车?那为什么在2.1小结中,当表被读锁时,其他session无法插入数据呢?这个其实和锁定方式有关,我们来看一下。仍然有两个窗口。首先,我们给第一个窗口的表加一个读锁,如下:locktableuserreadlocal;可以看到,最后多了一个local,就是关键。接下来,我们尝试在窗口2进行读写操作,如下:从图中可以看出,读写操作都可以顺利执行。但是此时如果我们去窗口1执行查询,如下:可以看到,刚刚在窗口2添加的数据在这里是看不到的。也就是说,窗口2中添加的数据对窗口1是无效的,必须等待窗口1中的锁被释放,才能看到窗口2中添加的数据。如下图所示,释放锁后,你可以在另一个窗口中看到添加的数据:这是默认的concurrent_insert行为,我将向你展示。也可以通过如下SQL修改该值:setglobalconcurrent_insert=ALWAYS;2.4lockedPriority在MyISAM中,默认写锁的优先级较高,但开发者也可以自行调整这个默认锁的优先级。话虽如此,由于MyISAM是表锁,所以不建议在需要频繁更新的场景下使用,否则可能会造成长时间的锁等待。因此,以下优先级调整仅供技术讨论。要修改SQL的优先级,首先我们可以在执行SQL的时候修改它的优先级:比如在执行select的时候,我们可以使用HIGH_PRIORITY来提高语句的优先级,如下:在执行delete/update/insert操作的时候,可以使用LOW_PRIORITY降低其优先级,使读操作先执行:当然,我们也可以使用如下SQL,让所有支持LOW_PRIORITY选项的语句默认按照低优先级处理。setLOW_PRIORITY_UPDATES=1修改写锁的上限。我们可以修改MAX_WRITE_LOCK_COUNT的值。该变量的默认值如下图所示:该值表示当一个表的写锁数量达到给定值时,会降低写锁的优先级,让读锁有机会执行。如果需要,我们可以自行调整这个值。调整方法如下:setGLOBALMAX_WRITE_LOCK_COUNT=1024;3.行级锁行级锁说到InnoDB宋哥就跟大家聊一聊。今天就说这么多~参考资料:1.https://database.51cto.com/art/201910/604421.htm2.https://zhuanlan.zhihu.com/p/123962424本文转载自微信公众号《江南一点雨》,可通过关注以下二维码获取。转载本文请联系江南一点鱼公众号。