mysql中有哪些锁?
共享锁(Shared Locks 或 S 锁):
共享锁允许多个事务同时读取同一数据,但不允许任何事务在同一数据上进行写操作;
排他锁(Exclusive Locks 或 X 锁):
排他锁阻止其他事务读取或修改同一数据,当一个事务需要更新或删除数据时,它会获取排他锁;
意向共享锁(Intent Shared Locks 或 IS 锁)&& 意向排他锁(Intent Exclusive Locks 或 IX 锁):
加锁的时候可能锁某一行或几行的数据,也可能锁整个表,但共享锁只能和共享锁兼容,排他锁和两者(共享锁、排他锁)都不兼容。举一个极端的例子,如果共享锁锁表(全表扫描查询),难道需要一行行遍历看数据是否被排他锁锁过吗?同理,排他锁锁表时,就需要一行行遍历是否存在数据被共享锁或者排他锁占用。
所以提出了IS锁和IX锁,减少行级锁和表级锁的冲突,以下是共享锁、排他锁、意向共享锁、意向排他锁的兼容关系
X(排他锁) IX(意向排他锁) S(共享锁) IS(意向共享锁) X ❌ ❌ ❌ ❌ IX ❌ ✅ ❌ ✅ S ❌ ❌ ✅ ✅ IS ❌ ✅ ✅ ✅ Tip: IX和IX能兼容是因为多个IX锁时获取不同行数据的行锁,可以同时存在;同理,IX和IS兼容也是一样;
记录锁(Record Locks):
锁定特定记录或行,锁定的行数是固定的;
间隙锁(Gap Locks):左开右开
间隙锁的目的是在RR级别下,防止幻读,幻读的产生是当前事务多次的查询结果的数量不一致;间隙锁的目的就是保证当前范围内的数据不会被更改,所以它会锁住某些区间的数据;
临键锁(Next-Key Locks):左开右闭
等于记录锁+间隙锁,所以我们只需要知道两个锁的定义就行,Mysql在隔离级别为RR时,默认是Next-key锁;
表级锁(Table Locks):
行级锁(Row Locks):
这种锁是最细粒度的锁,只锁定被事务访问的具体行。Innodb存储引擎支持行级锁;
页级锁(Page Locks):
页级锁锁定的是数据库页,这是介于行级锁和表级锁之间的中间级别。某些存储引擎可能会使用页级锁,但InnoDB主要使用行级锁;
索引底层是如何实现的?
Mysql中默认的存储引擎是Innodb,其索引是使用B+树实现的。B+树是一种多路搜索树,它的叶子节点存储了所有的数据行信息,叶子节点之间使用指针连接,方便范围查询和排序等操作,非叶子节点存储的是索引字段的值(主键索引存存的是id);
为什么要选择B+树:
查询效率:
- 内存:B+树的非叶子节点不存放实际的记录数据,仅存放索引,因此数据量相同的情况下,相比B树(即存数据又存索引)大大节省了内存空间
- IO操作:B+树的层级比B树更少,查询所需的IO操作更少;
插入和删除效率:
B+树相比于B树新增和删除节点不用复杂的结构变化;
查询范围:
B+树的叶子节点有指针连接方便进行范围查找
聚簇索引和非聚簇索引的区别?
聚簇索引和非聚簇索引的区别主要在数据的存储和查询:
数据存储:
聚簇索引(也即主键索引)的叶子节点存储的是主键对应的一行完整的数据,非聚簇索引(非主键索引)的叶子节点存储的是主键值,然后回表查询主键信息;
范围查询:
聚簇索引中的数据行与索引行一一对应的,因此聚簇索引通常比非聚簇索引更适合范围查询,而非聚簇索引需进行而进行二次查询:首先查找索引,然后查找数据行,这会导致性能问题,特别是大型表上进行范围查询;
什么情况会导致索引失效?
- 不符合最左匹配原则
- 使用列运算
- 使用函数
- 使用类型转换
- is not null
- 错误的模糊查询
mysql事务有哪些特性?
- 原子性(A):事务中执行的操作要么全部成功,要么全部失败;
- 一致性(C):事务执行前后事务的一致性没有被破坏,只能从一个一致性状态到另一个一致性状态;这里常说A转账B的问题,不管转账成功失败,A+B的总金额应该是不变的
- 隔离性(I):事务之间相互隔离,每个事务对其他事务的操作都是透明的;
- 持久性(D):事务完成后,对数据库的修改将永久保存在数据库中,即使系统故障也不会丢失;
如何保证事务四大特性(ACID)?
- 原子性:原子性是通过undo log(回滚日志)保证,InnoDB使用日志(undo log)来记录事务从开始到结束的所有操作,当事务提交失败时,通过undo log日志来回滚执行的数据,来确保事务的原子性;
- 持久性:持久性是通过redo log(重做日志)保证,在事务提交之前,innodb会将事务的修改操作先写入redo log 中,然后再将数据写入磁盘。即使在系统崩溃或断电的情况下,innodb可以通过重放事务日志来恢复数据,来确保事务的持久性。
- 隔离性:MVCC(多版本并发控制)和锁机制来保证;
- 一致性:一致性是通过各种约束,如主键,唯一性约束等,同时加上事务的持久性、原子性、隔离性同时来保证一致性;
不可重复读和幻读的区别?
不可重复读是指多次查询同一行数据,每次查询出来的结果都不同;
幻读是指范围查询时,会出现数据的个数总是在变化;
解决方案: 不可重复读通常通过提高隔离级别为RR或者手动使用行锁;
幻读:通过提高隔离级别为串行或者手动
SELECT ... FOR UPDATE
或SELECT ... LOCK IN SHARE MODE
时,InnoDB会在行级加锁的同时,也可能加间隙锁
MVCC是什么?能解决幻读吗?
MVCC是一种并发控制机制,主要用来解决幻读的问题。MVCC的核心思想是将每个事务的读操作与写操作解耦,通过保存数据的历史版本来控制并发,每个事务都会创建一个读视图(read-view),用于确定在事务开始时可见的数据版本,旧版本的事务会保存在undo log中(mysql有undo log缓存)
- 读操作:当一个事物执行SELECT语句时,会根据读视图的版本号与数据行的版本号就行比对,只读取在事务之前已经提交的数据行。这样,即使其他事务正在并发的插入或者删除数据,事务仍然可以读取到一致的数据。
- 写操作:当一个事务执行INSERT、UPDATE或DELETE语句时,会生成新的数据版本,并将旧版本的数据保存在undo log中.这样,其他事务在读取数据时仍然可以访问到旧版本的数据,从而避免幻读的问题;
RR+MVCC能解决幻读吗?
不能,即使是在RR级别下,MVCC虽然能解决大部分的幻读问题,但依然存在部分幻读问题无法解决;
在RR模式下,mysql 的innodb引擎读取数据有两种方式快照读(Snapshot Read)和当前读(Current Read);
- 快照读:读取开启事务时的已经提交的数据,即使后面有修改,当前事务也只能读取这个快照版本之前的事务数据;
- 当前读:通过
SELECT ... FOR UPDATE
或SELECT ... LOCK IN SHARE MODE
方式执行的查询,会读取最新提交的数据,而不是开启事务之前版本的数据;TIP:快照读主要适用于需要读取一致性数据的场景,比如报表生成或分析,其中数据的一致性比绝对实时性更重要;当前读适用于需要读取最新数据的场景,比如实时交易系统或需要立即反应最新数据变化的业务逻辑;
解决的方案:
- 使用串行化(Serializable)隔离级别:官方推荐方案,但这种解决方案,并发性能比较低。
- RR + 锁:使用 RR 隔离级别,通过
SELECT ... FOR UPDATE
加锁,可以保证在查询过程中,不会有别的事务插入数据;
如何保证数据库和缓存双写一致?
解决缓存和数据库一致问题的常见解决方案有以下 4 种:
- 先修改数据库,后更新缓存。
- 先更新缓存,后修改数据库。
- 先修改数据库,后删除缓存。
- 先删除缓存,后修改数据库。
前3种都存在第一步执行成功第二步执行失败的问题,所以一般都是使用第4种,先删除缓存在更新数据库的方案,最起码能保证数据的一致性问题;但是这种方案其实也有一些问题,会存在旧值覆盖缓存的问题(先删了缓存,另一个线程看没有缓存,查库更新缓存,导致缓存中是旧值,数据库是新值);如果我们想要达到最大程度的解决双写一致性问题,最终的解决方案就是MQ+延迟双删策略;
- 引入MQ保证消息不回丢失;
- 通过先删除缓存,修改数据库;
- 延时一段时间再次删除缓存;
Undo log(回滚日志) 和 redo log(重做日志)是由什么区别?什么时候落盘的?
- Undo log保证了事务的原子性;redo log保证事务的持久性;
- undo log在事务开始修改数据时就会生成undo log日志记录,主要记录的是修改前的数据;redo log则是事务开始修改数据时,主要记录的执行的sql;
- Redo log需要实时的flush到磁盘(Redo log的flush写入磁盘时机是灵活的,InnoDB会根据配置和系统状态决定何时将Redo log从内存flush到磁盘,通常是在事务提交前后、定期或当Redo log buffer达到一定比例时);undo log不需要实时的落盘,因为undo log是事务的一部分,事务提交后仍需要保存一段时间,以便其他事务能够读取旧的行版本(MVCC)。