MYSQL面试常问题

9 阅读7分钟

1.MySQL 存储引擎 InnoDB 和 MyISAM 区别

  • innoDB:支持事务、行锁、外键、崩溃可恢复
  • MyISAM:不支持事务,表锁,查询更快,不支持崩溃恢复

详细区别

1.事务支持

  • innoDB:支持事务(ACID),可以commit/rollback
  • MyISAM:不支持事务

2.锁机制

  • Inn哦DB:行级锁+表锁 并发高,适合大量更新
  • M有ISAM:表级锁 一写全锁,并发差

3.外键

  • innoDB:支持外键
  • MyISAM:不支持外键

4.崩溃恢复

  • innoDB:支持crash-safe,redo/undo日志保证数据不丢失
  • MyISAM:崩溃后易损坏,需要修复

5.count(*)效率

  • MyISAM极快:总行数存在表meta里
  • innoDB慢:要全表扫描统计

6.索引结构

  • 都是B+Tree
  • innoDB:聚簇索引,数据存在主键索引叶子节点
  • MyISAM:非聚簇索引,索引和数据分开

7.适用场景

  • innoDB:绝大多数业务 增删改多、需要事务、并发高、要求数据安全
  • MyISAM:制度/极少写、统计类 纯查询、日志表、无需事务的简单表

总结

  • innoDB支持事务、行锁、外键、崩溃恢复
  • MyISAM不支持事务,只支持表锁,查询快但不安全
  • 现在默认、体检、几乎都用innoDB
  • MyISAM只有在极高读、几乎不写的场景才考虑

2.事务四大特性 ACID

A:Atomicity原子性

  • 事务是最小执行单位,不可再分
  • 要么全部执行成功,要么全部失败回滚
  • 不会出现只执行一半的情况

C:Consistency一致性

  • 事务执行前后,数据库的完整性约束不变
  • 数据从一个合法状态,转到另一个合法状态
  • 比如转账前后总金额不变

I:Isolation隔离性

  • 多个事务并发执行时,互相不可干扰
  • 一个事务看不到另一个事务的中间状态
  • 通过隔离级别实现(读未提交、读已提交、可重复读、串行化)

D:Durability持久性

  • 事务一旦提交陈工,对数据的修改就是永久的
  • 即使数据库崩溃、断电、数据也不会丢失

总结

原子性保证操作不被打断,一致性保证数据合法,隔离性保证并发互不干扰,持久性保证提交后永久生效

3.事务隔离级别

四大隔离级别(从低到高)

  1. 读未提交(Read Uncommitted)
  2. 读已提交(Read Committed)——Oracle默认
  3. 可重复读(Repeatable Read)——MySQL InnoDB默认
  4. 串行化(Serializable)

对应会出现的问题

  • 脏读:督导别的事务未提交的数据
  • 不可重复读:同一事务内,两次查询结果不一样
  • 幻读:同一事务内,查询到了新增/删除的行

级别与问题对照表

隔离级别脏读不可重复读幻读
读未提交✅ 会✅ 会✅ 会
读已提交❌ 不会✅ 会✅ 会
可重复读❌ 不会❌ 不会✅ 会(InnoDB 靠 MVCC 很大程度解决)
串行化❌ 不会❌ 不会❌ 不会

总结

  1. 读未提交:最低级,允许读到未提交数据,会脏读
  2. 读已提交:只能读到已提交数据,解决脏读,但会不可重复读。
  3. 可重复读:同一事务内多次读取结果一致,解决不可重复读,时MySQL默认。
  4. 串行化:最高级,完全串行执行,无并发问题,但性能最差。

4.索引是什么?为什么快?

1.索引是什么

索引是数据库中用于加速查询的数据结构,本质是排好序的快速查找结构。

MySQL InnoDB索引默认是B+树

可以理解为:

数据库的目录,不用再从头到尾翻书(全表扫描)

2.索引为什么快

A.避免全表扫描

  • 无索引:查询要逐行遍历,O(n)
  • 有索引:走B+树查找,O(log n)。数据量越大,差距越恐怖。

B.B+树本身结构极适合磁盘查询

  • 层级少:几百万数据只需3~4层
  • 叶子节点有序且连成链表
    • 范围查询wherer id between 100 and 200极快
    • 顺序遍历不用跳来跳去
  • 只在叶子节点存数据,树更小、缓存命中率更高

C.索引本身有序

  • 排序order by
  • 分组group by
  • 去重distinct

都可以直接利用有序性,避免额外排序。

D.减少磁盘I/O

数据库慢,90%都慢在磁盘I/O

索引通过树结构大幅减少I/O次数,所以速度提升巨大

总结

索引是排好序的B+树结构,通过避免全表扫描、减少磁盘I/O、利用树的快速查找特性,让查询从O(n)降到O(log n),所以极快。

5.唯一索引、主键索引、普通索引区别

1.主键索引(PRIMARY KEY)

  • 列的值必须唯一,而且不能为NULL
  • innoDB中主键索引就是聚簇索引,数据直接存在索引叶子节点
  • 一般用id做主键

2.唯一索引(UNIQUE KEY)

  • 一张表可以有多个
  • 列的值必须唯一,但允许NULL(且多个NULL不冲突)
  • 是非聚簇索引,叶子节点存主键值

3.普通索引(INDEX/KEY)

  • 一张表可以有多个
  • 没有任何约束,值可以重复、可以为NULL
  • 只用来加速查询

对照表

类型个数限制值是否唯一能否 NULL索引类型
主键索引只能 1 个唯一不能聚簇索引
唯一索引可多个唯一能(多个 NULL)非聚簇索引
普通索引可多个可重复非聚簇索引

使用场景

  • 主键索引:必须有,业务唯一标识(id)
  • 唯一索引:业务上不能重复,但不是主键(手机号、身份证)
  • 普通索引:只用于加速查询,允许重复(性别、状态)

总结

主键只能一个,非空唯一却聚簇;唯一索引可多个,值唯一允许NULL;普通索引无约束,只提速。

6.left join /inner join /right join 区别

1.inner join(内连接)

只保留两张表中能匹配上的数据

  • A有、B没有→不显示
  • B有、A没有→不显示
  • 只有两边都有的记录才会出现

select * from A inner join B on A.id=B.a_id;

2.left join(左连接)

左表数据完全保留,右表能匹配的就显示,匹配不上为NULL

  • 左表(A)所有记录一定都在
  • 右表(B)匹配不到的字段显示NULL

select * from A left join B on A.id=B.a_id;

3.right join(右连接)

右表数据全部保留左边能匹配就能显示,匹配不上为NULL

  • 右表(B)所有记录一定都在
  • 左表(A)匹配不到的字段显示NULL

select * from A left join B on A.id=B.a_id;

简记

  • inner join:取交集

  • left join:左表全部+匹配的右表

  • rigth join:右表全部+匹配的左表

  • 想要必须存在的关联数据→inner join

  • 想要左边表数据一定全显示→left join(常用)

  • right join很少用,一般改成left join看着更舒服

7.简单 SQL 手写:增删改查、分页、分组、排序

  1. 增(INSERT)

INSERT INTO user(id,name,age) VALUES(1,'张三',20);

  1. 删()

DELETE FROM user WHERE id=1;

  1. 改()

UPDATE user SET name='李四',age=21 WHERE id=1;

  1. 查()

SELECT id,name,age FROM user WHERE age>18;

  1. 排序()

SELECT * FROM user ORDER BY age DESC;

  1. 分组()

SELECT dept,COUNT(*) FROM user GROUP BY dept;

  1. 分组后筛选()

SELECT dept,COUNT(*) FROM user GROUP BY dept HAVING COUNT(*)>5;

  1. 分页()

MYSQL分页固定写法:

SELECT * FROM user LIMIT 偏移量, 每页条数; 
-- 第1页,每页10条 
SELECT * FROM user LIMIT 0, 10;
-- 第2页
SELECT * FROM user LIMIT 10, 10;

8.SQL 优化简单说几点

  1. 避免SELECT*,只查需要的字段
  2. where条件建索引,少用模糊查询%xxx%
  3. 避免索引失效:不做运算、不类型转换、不用!=、or慎用
  4. 少用子查询,优先使用join代替
  5. 分页优化:大偏移量用id>last_id方法
  6. group by/order by尽量用索引字段
  7. 避免全表扫描,小表驱动大表
  8. 合理建联合索引,遵循最左前缀原则