1、面试题
数据库锁有哪些类型?锁是如何实现的?MySQL行级锁有哪两种?一定会锁定指定的行么?为什么?悲观锁和乐观锁是什么?使用场景是什么?mysql死锁原理以及如何定位和解决?
2、面试官心里分析
说实话,聊mysql的话,我肯定也是循序渐进慢慢问的,先聊下存储引擎,然后问问索引一半怎么用,一半用innodb存储引擎,加上联合索引能玩儿好,明白什么是聚簇索引,再熟悉事务那套东西,包括spring的事务传播之类的,那么数据库常见的开发都没问题了。
接着就是聊聊锁,因为如果对锁没了解的话,线上系统其实进场有时候,在高并发访问下,会出现一些死锁的问题,或者是等待锁时间过长就超时了,偶尔会有这种问题的,所以会问问你锁的问题。
3、面试题剖析
(1)mysql锁
先跟面试官聊下,mysql的锁类型吧,一般其实就是表锁、行锁和页锁。
一般myisam会加表锁,就是myisam引擎下,执行查询的时候,会默认加个表共享锁,也就是表读锁,这个时候别人只能来查,不能写数据的;然后myisam写的时候,也会加个表独占锁,也就是表写锁,别人不能读也不能写。
这个myisam因为很少用了,所以别去管他了,面试的时候来这么一句就ok了。所以话说回来,大家也发现了,myisam其实在实际生产中,我们曾经就是在报表系统里用的是最多的,当年es和kylin没出来的时候,大数据系统计算好的报表数据,都是放mysql的myisam里的,一般就是每天凌晨导入一批数据,那个时候别人不需要查询,没人凌晨来看报表;然后白天也没有写入,就是别人纯查询,建好索引,查询性能还是不错的,单表支撑千万级别数据没问题。
报表系统,有一次,一般来说hadoop计算完大批量的报表数据在凌晨就算完了,没有人看报表的;但是确实有一次是hadoop出了问题,是在上午11点还在计算往表里面大规模大批量的插入数据,当时造成了很严重的锁表,别人查就查不出来,我们的报表系统的用户在查看报表的时候,点504,点504,超时。
超大的case。。。。
这个页级锁,一般几乎很少用,你提一句就ok了,我们不多说了。
其实面试官重点还是跟你聊聊行锁就好了,是innodb引擎一般用行锁,但是也有表锁。
innodb的行锁有共享锁(S)和排他锁(X),两种,其实说白了呢,共享锁就是,多个事务都可以加共享锁读同一行数据,但是别的事务不能写这行数据;排他锁,就是就一个事务可以写这行数据,别的事务只能读,不能写。
innodb的表锁,分成意向共享锁,就是说加共享行锁的时候,必须先加这个共享表锁;还有一个意向排他锁,就是说,给某行加排他锁的时候,必须先给表加排他锁。这个表锁,是innodb引擎自动加的,不用你自己去加。
insert、update、delete,innodb会自动给那一行加行级排他锁
select,innodb啥锁都不加,因为innodb大家记得么,默认实现了可重复读,也就是mvcc机制,所以多个事务随便读一个数据,一般不会有冲突,大家就读自己那个快照就可以了,不涉及到什么锁的问题
但是innodb从来不会自己主动加个共享锁的,除非你用下面的语句自己手动加个锁:
手动加共享锁:select * from table where id=1 lock in share mode,那你就给那一行加了个共享锁,其他事务就不能来修改这行数据了
手动加排他锁:select * from table where id=1 for update,那你就给那一行加了个排他锁,意思就是你准备修改,别的事务就别修改了,别的事务的修改会hang住。这个要慎用,一般我们线上系统不用这个,容易搞出问题来。
所以看到这儿,我们琢磨琢磨默认的数据库锁机制,各位同学
对一行数据,如果有人在修改,会加个排他锁,然后你不能修改,你只能等着获取这把锁,但是这个时候你可以随便select,你就是查询你的事务开始之前那行数据的某个版本而已。然后如果你修改某行数据,会同时拿这个表的排他锁,但是呢,如果不同的事务修改不同的行,会拿不同行的行级排他锁,但是大家都会拿一个表的排他锁,ok,实际上innodb的表级排他锁可以随便拿,这个是没冲突的。
所以这个就是mysql innodb存储引擎的默认锁模式,其实还挺不错的。相当于就是一行数据,同一个时刻只能一个人在修改,但是别人修改,你可以随便读,读是读某个版本的,走mvcc机制。大家理解这个就好。
(2)悲观锁和乐观锁是啥?
mysql里的悲观锁是走select * from table where id=1 for update,就这个,意思是我很悲观,我担心自己拿不到这把锁,我必须先锁死,然后就我一个人可以干这事儿,别人都干不了了,不能加共享锁,也不能加排他锁。
乐观锁,就是说我觉得应该没啥问题,我修改的时候感觉差不多可以获取到锁,不需要提前搞一把锁,我就先查出来某个数据,select id,name,version from table where id=1,接着再执行各种业务逻辑之后再修改,update table set name=’新值’,version=version+1 where id=1 and version=1,就是说每次修改,比较一下这条数据的当前版本号跟我之前查出来的版本号是不是一样的,如果是一样的就修改然后把版本号加1,否则就不会更新任何一行数据,此时就重新查询后再次更新。
一般悲观锁什么时候用呢?比如你查出来了一条数据,要在内存中修改后再更新到数据库中去,但是如果这个过程中数据被别人更新了,你是不能直接干这个操作的,这个时候,你就得走上面那个操作,查询之后就不让别人更新了,你搞完了再说。
但是真有这种场景,推荐你还是用乐观锁把,悲观锁实现简单一点,但是太有风险了,很容易很容易死锁,比如事务A拿了数据1的锁,事务B拿了数据2的锁,然后事务A又要获取数据2的锁就会等待,事务B又要获取数据1的锁,也会等待,此时尴尬了,死锁,卡死,互相等待,永不释放。
所以select ... for update这个语法,轻易不要用,我们几乎线上很少用。
(3)死锁
事务A
select * from table where id=1 for update
事务B
select * from table where id=2 for update
事务A
select * from table where id=2 for update
事务B
select * from table where id=1 for update
常见的死锁就是类似上面那种,给大家说过了,分别都持有一个锁,结果还去请求别人持有的那把锁,结果就是谁也出不来,死锁了
情况太多,不一一列举了,其实就给大家说下发现死锁的时候怎么排查吧
其实很简单,就是找dba看一下死锁日志,就ok了,然后根据对应的sql,找下对应的代码,具体判断一下为啥死锁了