共计 4273 个字符,预计需要花费 11 分钟才能阅读完成。
(一)MySQL 基础考点
-
事务的原理 特性及事务并发控制
1.1 什么是事务(Transaction)?- 事务是数据库并发控制的基本单位 - 事务可以看作是一些列 SQL 语句的集合 - 事务必须要么全部执行成功,要么全部执行失败(回滚) - 事务使用常见的场景: 银行转账操作
1.2 事务的 ACID 特性
- 原子性(Atomicity):一个事务中所有操作全部完成或失败
- 一致性(Consistency):事务开始和结束之后数据完整性没有被破坏
- 隔离性(Isolation):允许多个事务同时对数据库修改和读写
- 持久性(Durability):事务结束之后,修改是永久的不会被丢失
1.3 事务并发控制
1.3.1 可能产生哪些问题?- 幻读:一个事务第二次查出现第一次没有的结果
- 非重复读:一个事务重复读两次得到不同的结果
- 脏读:一个事务读取到另一个事务没有提交的修改
- 丢失修改:并发写入造成其中一些修改丢失
1.3.2 解决并发异常,定义 4 种事务隔离级别
- 读未提交:别的事务可以读取到未提交改变
- 读已提交:只能读取已经提交的数据
- 可重复读:同一个事务先后查询结果一样(Mysql innoDB 默认实现可重复读级别)
- 串行化:事务完全串行化执行,隔离级别最高,效率最低
1.3.3 如何解决高并发场景下的插入重复(写入数据库会出现重复问题)
- 使用数据库的唯一索引
- 使用队列异步写入
- 使用 redis 等实现分布式锁
1.3.4 乐观锁和悲观锁
- 悲观锁:先获取锁再进行操作。一锁二查三更新 select for update
- 乐观锁:先修改,更近的时候发现数据已经变了就回滚(check and set)
- 使用需要根据响应速度、冲突频率、重试代价来判断使用哪一种
-
常见字段的含义及区别
2.1 文本型- CHAR - VARCHAR - TINYTEXT - TEXT
2.2 数值型
- TINYINT
- SMALLINT
- INT
- SIGINT
- FLOAT
- DOUBLE
2.3 日期和时间
- DATE
- DATETIME
- TIMESTAMP (4 个字节,但接受的时间 1970-2038 年之间)
-
常见数据库引擎之间的区别(InnoDB VS MyISAM)
- MyISAM 不支持事务,InnoDB 支持事务
- MyISAM 不支持外键,InnoDB 支持外键
- MyISAM 只支持表锁,InnoDB 支持行锁和表锁
- MyISAM 支持全文索引,InnoDB 不支持
(二)Mysql 索引原理及优化常见考题
-
索引的原理、类型、结构
1.1 什么是索引?- 数据表种一个或者多个列进行排序的数据结构 - 索引能够大幅提升索引速度 - 创建、更新索引本身也会消耗空间和时间
1.2 什么是 B -Tree?(查找结构进化史)
- 多路平衡查找树
1.3 B+Tree
- Mysql 实际使用的 B +Tree 作为索引的数据结构
-
创建索引的注意事项,使用原理
2.1 常见索引类型- 普通索引 CREATE INDEX - 唯一索引,索引列的值必须唯一 CREATE UNIQUE INDEX - 多列索引(联合索引) - 主键索引 一个表只能有一个 PRIMARY KEY - 全文索引 InnoDB 不支持(一般采用专门的全文索引数据库实现)
2.2 什么时候创建索引?(建表的时候需要根据查询需求来创建索引)
- 经常用作查询条件的字段(WHERE 条件)
- 经常用作表连接的字段
- 经常出现在 order by, group by 之后的字段
2.3 创建索引有哪些需要注意的?(最佳实践)
- 非空字段 NOT NULL, Mysql 很难对空值做查询优化
- 区分度高,离散度大,作为索引的字段值尽量不要有大量相同值
- 索引的长度不要太大(比较消耗时间 -- 索引作为 B +Tree 的 key 值存在, 字符串 key 太长比较耗时)
2.4 索引什么时候失效?- 记忆口诀:模型匹配、类型隐转、最左匹配
- 以 % 开头的 LIKE 语句,模糊搜索
- 出现隐式转换(python 这种动态语言查询中需要注意)
- 没有满足最左前缀原理(针对联合索引)
2.5 什么聚集索引和非聚集索引
- 是指 B +Tree 叶节点存的是指针还是数据记录
- MyISAM 索引和数据分离,使用的是非聚集索引(存的是数据指针)
- InnoDB 数据文件就是索引文件,主键索引就是聚集索引
-
如何排查和消除慢查询
3.1 慢查询通常是缺少索引,索引不合理或者业务代码实现导致
3.2 排查- slow_query_log_file 开启并且查询慢查询日志 - 通过 explain 命令排查索引问题 - 调整数据修改索引;业务代码层限制不合理访问(比如一次获取太多数据 -- 实现分页; 数据类型不匹配导致全文扫描)
(三)SQL 语句编写常考题
- 常用连接为重点
1.1 内连接(INNER JOIN): 两个表都存在匹配时,才会返回匹配行
1.2 外连接(LEFT/RIGHT JOIN):返回一个表的行,即使另一个没有匹配
1.3 全连接(FULL JOIN):只要某一个表存在匹配就返回
(四)非关系型数据库 Redis
-
缓存 (内存缓存) 的使用场景
1.1 为什么要使用缓存?- 缓解关系数据 (常见的 Mysql) 并发访问的压力: 热点数据 - 减少响应时间:内存 IO 速度比磁盘快 - 提升吞吐量:Redis 等内存数据库单机就可以支持很大并发
1.2 Redis 和 Memcached 主要区别?- 数据存储类型:redis 支持 string/List/hash/set/sort set;memcached 只支持文本型 / 二进制类型
- 网络 IO 模型:redis 单进程模式;memcached 多线程、非阻塞 IO 模式
- 持久化支持:redis 支持两种 RDB,DOF; memcached 不支持
-
Redis 常用数据类型和使用场景?
2.1 数据类型- String(字符串):用来实现简单的 KV 键值对存储,比如计数器
- List(链表): 实现双向链表,比如用户的关注,粉丝列表
- Hash(哈希表):用来存储彼此相关信息的键值对
- Set(集合):存储不重复元素,比如用户的关注者
- Sorted set(有序集合):实时信息排行榜
2.2 支持两种方式实现持久化
- 快照方式:把树快照放在磁盘二进制文件中,dump.rdb
- AOF: 每一个写命令追加到 appendonly.aof 中
- 可以修改通过 Redis 配置实现
2.3 什么 redis 事务?- 将多个请求打包,一次性,按序执行多个命令的机制
- 通过 MULTI, EXEC,WATCH 等命令实现事务功能
2.4 如何实现分布式锁?- 使用 setnx 实现加锁,可以同时通过 expire 添加超时时间
- 锁的 value 值可以使用一个随机的 uuid 或者特定的命名
- 释放锁的时候,通过 uuid 判断是否是该锁,是则执行 delete 释放锁
-
缓存使用的坑
3.1 使用缓存的模式?- Cache Aside: 同时更新缓存和数据库 - Read/Write Through: 先更新缓存,缓存负责同步更新数据库 - Write Behind Caching: 先更新缓存,缓存定期异步更新数据库
3.2 如何解决缓存穿透问题?- 原因:由于大量缓存查不到就去数据库取,数据库也没有要查的数据
- 解决:对于没有查到返回 None 的数据也缓存; 插入数据的时候删除相应缓存,或者设置较短的超时时间
3.3 如何解决缓存击穿问题?- 原因:某些非常热点的数据 key 过期,大量请求达到后端数据库
- 解决:分布式锁 - 获取锁的线程从数据库拉数据更新缓存,其他线程等待
异步后台更新 - 后台任务针对过期 key 自动刷新
3.4 如何解决缓存雪崩问题?- 原因:缓存不可用或大量缓存 key 同时失效,大量请求直接达到数据库
- 解决:多级缓存 -- 不同级别的 key 设置不同的超时时间
随机超时 --key 的超时时间随机设置,防止同时超时
架构层 -- 提升系统可用性,监控、报警完善
(五)Mysql 与 Redis 练习题
-
Mysql 思考题
- 为什么 Mysql 数据库主键使用自增的整数比较好?uuid 可以吗?
在最佳实践中,auto_increment 字段长度比 uuid 小,从性能及可读性都比 uuid 要好 - 如果是分布式系统下我们怎么生成数据库的自增 id 呢?
在 auto_increment 的基础上,设置 step 增长步长;比如:Master1 生成的是 1,4,7,10,
Master2 生成的是 2,5,8,11 Master3 生成的是 3,6,9,12。
这样就可以有效生成集群中的唯一 ID,也可以大大降低 ID 生成数据库操作的负载。
- 为什么 Mysql 数据库主键使用自增的整数比较好?uuid 可以吗?
-
Redis 应用 - 分布式锁
- 编写一个简单的分布式锁,要求支持超时时间参数
import time
import redis
class RedisLock(object):
def __init__(self, key, timeout):
self.rdcon = redis.Redis(host='', port=6379, password="", db=1)
self._lock = 0
self.timeout = timeout
self.lock_key = "%s_dynamic_test" % key
@staticmethod
def get_lock(cls):
while cls._lock != 1:
timestamp = time.time() + self.timeout + 1
cls._lock = cls.rdcon.setnx(cls.lock_key, timestamp)
# 注意下方括号的范围
if cls._lock == 1 or (time.time() > cls.rdcon.get(cls.lock_key) and time.time() > cls.rdcon.getset(cls.lock_key, timestamp)):
print "get lock"
break
else:
time.sleep(0.3)
@staticmethod
def release(cls):
if time.time() < cls.rdcon.get(cls.lock_key):
print("release lock")
cls.rdcon.delete(cls.lock_key)
def deco(cls):
def _deco(func):
def __deco(*args, **kwargs):
print("before %s called [%s]."%(func.__name__, cls))
cls.get_lock(cls, timeout)
try:
return func(*args, **kwargs)
finally:
cls.release(cls)
return __deco
return _deco
@deco(RedisLock("key"))
def myfunc():
# do_something
time.sleep(20)
if __name__ == "__main__":
myfunc()
正文完