后端数据库攻略

25次阅读

共计 4273 个字符,预计需要花费 11 分钟才能阅读完成。

(一)MySQL 基础考点

  1. 事务的原理 特性及事务并发控制
    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)
        - 使用需要根据响应速度、冲突频率、重试代价来判断使用哪一种
  1. 常见字段的含义及区别
    2.1 文本型

    - CHAR
    - VARCHAR
    - TINYTEXT
    - TEXT
2.2 数值型
    - TINYINT
    - SMALLINT
    - INT
    - SIGINT
    - FLOAT
    - DOUBLE
2.3 日期和时间
    - DATE
    - DATETIME
    - TIMESTAMP (4 个字节,但接受的时间 1970-2038 年之间)

  1. 常见数据库引擎之间的区别(InnoDB VS MyISAM)

    • MyISAM 不支持事务,InnoDB 支持事务
    • MyISAM 不支持外键,InnoDB 支持外键
    • MyISAM 只支持表锁,InnoDB 支持行锁和表锁
    • MyISAM 支持全文索引,InnoDB 不支持

(二)Mysql 索引原理及优化常见考题

  1. 索引的原理、类型、结构
    1.1 什么是索引?

    - 数据表种一个或者多个列进行排序的数据结构
    - 索引能够大幅提升索引速度
    - 创建、更新索引本身也会消耗空间和时间
1.2 什么是 B -Tree?(查找结构进化史)
    - 多路平衡查找树
1.3 B+Tree
    - Mysql 实际使用的 B +Tree 作为索引的数据结构
  1. 创建索引的注意事项,使用原理
    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 数据文件就是索引文件,主键索引就是聚集索引
  1. 如何排查和消除慢查询
    3.1 慢查询通常是缺少索引,索引不合理或者业务代码实现导致
    3.2 排查

    - slow_query_log_file 开启并且查询慢查询日志
    - 通过 explain 命令排查索引问题
    - 调整数据修改索引;业务代码层限制不合理访问(比如一次获取太多数据 -- 实现分页; 数据类型不匹配导致全文扫描)
    

(三)SQL 语句编写常考题

  1. 常用连接为重点
    1.1 内连接(INNER JOIN): 两个表都存在匹配时,才会返回匹配行
    1.2 外连接(LEFT/RIGHT JOIN):返回一个表的行,即使另一个没有匹配
    1.3 全连接(FULL JOIN):只要某一个表存在匹配就返回

(四)非关系型数据库 Redis

  1. 缓存 (内存缓存) 的使用场景
    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 不支持
  1. 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 释放锁
  1. 缓存使用的坑
    3.1 使用缓存的模式?

    - Cache Aside:  同时更新缓存和数据库
    - Read/Write Through: 先更新缓存,缓存负责同步更新数据库
    - Write Behind Caching: 先更新缓存,缓存定期异步更新数据库
3.2 如何解决缓存穿透问题?- 原因:由于大量缓存查不到就去数据库取,数据库也没有要查的数据
    - 解决:对于没有查到返回 None 的数据也缓存; 插入数据的时候删除相应缓存,或者设置较短的超时时间
3.3 如何解决缓存击穿问题?- 原因:某些非常热点的数据 key 过期,大量请求达到后端数据库
    - 解决:分布式锁 - 获取锁的线程从数据库拉数据更新缓存,其他线程等待
        异步后台更新 - 后台任务针对过期 key 自动刷新
3.4 如何解决缓存雪崩问题?- 原因:缓存不可用或大量缓存 key 同时失效,大量请求直接达到数据库
    - 解决:多级缓存 -- 不同级别的 key 设置不同的超时时间
        随机超时 --key 的超时时间随机设置,防止同时超时
        架构层 -- 提升系统可用性,监控、报警完善

(五)Mysql 与 Redis 练习题

  1. 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 生成数据库操作的负载。
  2. 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()

正文完
 0