数据库(五)

1. MySQL 基础考点

  • 事务原理,事务特性,事务并发控制
    • 常用字段、含义区别
    • 常用数据库引擎直接区别

2. 什么是事务?

MySQL 事务主要用于处理操作量大,复杂度高的数据。比如说,在人员管理系统中,你删除一个人员,你即需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成一个事务!

可以看做是一系列 SQL 语句的集合,事务要么全部执行成功,要么执行失败(回滚),比较常见的有银行转账。

  • 在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务。
  • 事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行。
  • 事务用来管理 insert、update、delete 语句

2.1 事务的 ACID 特性

  • 原子性(Atomicity):一个十五中所有操作全部完成或失败
  • 一致性(Consistency):事务开始或结束之后数据完整性没有被破坏
  • 隔离性(Isolation):允许多个事务同时对数据库修改和读写
  • 持久性(Duraility):事务结束后,修改是永久的不会丢失

2.2 事务控制语句

语句 说明 语句
begin/start transaction 开启事务 commit/commit work 提交事务(永久)
savepoint identifier savepoint 允许在事务中创建一个保存点(可有多个) release savepoint identifier 删除保存点(需指定保存点)
rollback to identifier 回滚到标记点 set transaction 设置事务隔离级别(innodb 隔离级别有:uncommitted/read committed/repeatable read 、 serializable

savepoint 保留点

savepoint 是在数据库事务处理中实现“子事务”(subtransaction),也称为嵌套事务的方法。事务可以回滚到 savepoint 而不影响 savepoint 创建前的变化, 不需要放弃整个事务。

ROLLBACK 回滚的用法可以设置保留点 SAVEPOINT,执行多条操作时,回滚到想要的那条语句之前。

mysql> select * from test;                                                                                     
+----+------+                                                                                                  
| id | name |                                                                                                  
+----+------+                                                                                                  
|  1 | rose |                                                                                                  
|  2 | lila |                                                                                                  
+----+------+                                                                                                  
2 rows in set (0.00 sec)                                                                                       
# 开启事务                                                                                             
mysql> begin;                                                                                                  
Query OK, 0 rows affected (0.00 sec)       

# 设置保留点
mysql> savepoint insert_before;                                                                                
Query OK, 0 rows affected (0.00 sec)                                                                                                                                                 
mysql> insert into test(name) values('john');       # 插入一条数据                                     
Query OK, 1 row affected (0.00 sec)                                                                            
mysql> select * from test;                                                                                     
+----+------+                                                                                                  
| id | name |                                                                                                  
+----+------+                                                                                                  
|  1 | rose |                                                                                                  
|  2 | lila |                                                                                                  
|  4 | john |                                                                                                  
+----+------+                                                                                                  
3 rows in set (0.00 sec)                                                                                       
                                                                                                       # 回滚到保留点
mysql> rollback to insert_before;                                                                              
Query OK, 0 rows affected (1.00 sec)                                                                           
# 数据没有被修改                                                                                        mysql> select * from test;                                                                                     
+----+------+                                                                                                  
| id | name |                                                                                                  
+----+------+                                                                                                  
|  1 | rose |                                                                                                  
|  2 | lila |                                                                                                  
+----+------+                                                                                                  
2 rows in set (0.00 sec)                                                                               

Tips:MySQL 命令行默认事务是自动提交的,即无需手动 commit,若要禁止自动提交,可设置 set autocommit=0

2.3 MYSQL 事务处理方法

1、用 BEGIN, ROLLBACK, COMMIT来实现

  • BEGIN 开始一个事务
  • ROLLBACK 事务回滚
  • COMMIT 事务确认

2、直接用 SET 来改变 MySQL 的自动提交模式:

  • SET AUTOCOMMIT=0 禁止自动提交
  • SET AUTOCOMMIT=1 开启自动提交
mysql> create table test(
    -> id int primary key auto_increment,
    -> name varchar(32))
    -> engine=innodb;
Query OK, 0 rows affected (1.62 sec)

mysql> select * from test;
Empty set (0.00 sec)

mysql> begin;       # 开启事务
Query OK, 0 rows affected (0.00 sec)

mysql> insert into test(name) values('rose');
Query OK, 1 row affected (0.00 sec)

mysql> insert into test(name) values('lila');
Query OK, 1 row affected (0.00 sec)

# 提交
mysql> commit;
Query OK, 0 rows affected (0.07 sec)

mysql> select * from test;
+----+------+
| id | name |
+----+------+
|  1 | rose |
|  2 | lila |
+----+------+
2 rows in set (0.00 sec)

事务回滚:

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into test(name) values('john');
Query OK, 1 row affected (0.00 sec)

mysql> rollback;
Query OK, 0 rows affected (0.13 sec)

mysql> select * from test;
+----+------+
| id | name |
+----+------+
|  1 | rose |
|  2 | lila |
+----+------+
2 rows in set (0.00 sec)

参考:https://www.runoob.com/mysql/mysql-transaction.html

3. 事务并发控制

如果不对事务进行并发控制,可能产生四种异常情况:

  • 幻读(phantom read):一个事务第二次查出现第一次没有的结果
  • 非重复读(nonrepeatable read):一个事物重复读两次得到视同结果
  • 脏读(dirty read):一个事务读取到另一个事务没有提交的修改
  • 丢失修改(lost update):并发写入造成其中一些修改丢失

4. 事务隔离级别

为了解决事务并发控制异常,定义了 4 种事务隔离级别:

  • 读未提交(read uncommitted):别的事务可以读取到未提交改变,脏读(Dirty Read),会导致很多问题,实际应用中很少使用。
  • 读已提交(read committed):只能读取已经提交的数据,大多数数据库系统默认隔离级别(MySQL 除外),一个事务从开始到提交之前,所做的任何修改对其他事物都是不可见的,只有提交后才可见。页脚不可重复读(nonrepeatable),因为两次执行同样的查询,可能会得到不一样的结果。
  • 可重复读(repeatable read):同一个事务先后查询结果一样,解决了脏读的问题,不能解决幻读(Phantom Read)问题,MySQL 默认事务隔离级别
  • 串行化(Serializable):事务完全串行化的执行,隔离级别最高,执行效率最低。强制事务串行执行,避免幻读问题,只有在非常需要确保数据一致性而且可以接收没有并发情况下,才考虑使用。

隔离级别越低通常可以执行更高的并发,系统开销有越低

幻读:当某个事物在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围内的记录时,会产生幻行(Phantom Row),InnoDB 和 XtraDB 引擎通过多版本并发控制(MVCC)解决了幻读问题

5. 解决高并发场景下的插入重复

高并发场景下,写入数据库会有数据重复问题,以下是几个避免重复插入的思路:

  • 使用数据库的唯一索引
  • 使用队列异步写入
  • 使用 Redis 等实现分布式锁

6. 乐观锁和悲观锁

乐观锁

总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。

先修改,更新时候发现数据已变了就回滚(check and set)

悲观锁

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

先获取锁再执行操作,一锁二查三更新(select for update)

使需要根据响应速度、冲突频率、重试代价来判断使用哪一种

乐观锁常见的两种实现方式

乐观锁一般会使用版本号机制或CAS算法实现。

一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

举一个简单的例子:
假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。

  1. 操作员 A 此时将其读出( version=1 ),并从其帐户余额中扣除 100-$50 )。
  2. 在操作员 A 操作的过程中,操作员B 也读入此用户信息( version=1 ),并从其帐户余额中扣除 100-$20 )。
  3. 操作员 A 完成了修改工作,将数据版本号加一( version=2 ),连同帐户扣除后余额( balance=$50 ),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。
  4. 操作员 B 完成了操作,也将版本号加一( version=2 )试图向数据库提交数据( balance=$80 ),但此时比对数据库记录版本时发现,操作员 B 提交的数据版本号为 2 ,数据库记录当前版本也为 2 ,不满足 “ 提交版本必须大于记录当前版本才能执行更新 “ 的乐观锁策略,因此,操作员 B 的提交被驳回。

这样,就避免了操作员 B 用基于 version=1 的旧数据修改的结果覆盖操作员A 的操作结果的可能。

参考文章:

  • 面试必备之乐观锁与悲观锁:https://www.imooc.com/article/details/id/44217

  • Mysql锁机制--乐观锁 & 悲观锁:https://www.cnblogs.com/cyhbyw/p/8869855.html

7. MySQL 常用数据类型

image
image

[图片上传失败...(image-7342b-1593095890096)]

8. InnoDB 和 MyISAM 引擎区别

  • MyISAM :不支持事务,不支持外键,只支持表锁
  • InnoDB :支持事务,支持外键,支持行锁和表锁

9. MySQL查找数据结构

  • 索引原理、类型、结构
  • 创建索引的注意事项,使用原则
  • 如何排查和消除慢查询

9.1 MySQL 查找结构进化史

[图片上传失败...(image-2b1fc0-1593095890096)]

9.2 B-Tree

MySQL 查找结构使用的是多路平衡查找树,也就是 B-Tree,那么为什么要用 B-Tree,它有什么优点:

  • 多路平衡查找树(每个节点最多 m(m>=2),称为 m 阶或者度)
  • 叶子节点具有相同的深度
  • 节点中的数据 key 从左到右是递增的

image

B+Tree

B+Tree 是 B-Tree 的变形:

  • MySQL 实际使用的 B+Tree 作为所有的数据结构
  • 只在叶子节点带有指向记录的指针(这样可以可以增加的树的度)
  • 叶子节点通过指针相连(这样可以实现范围查询)

[图片上传失败...(image-ce16f8-1593095890096)]

10. MySQL 索引

什么是索引

  • 索引是数据表中一个或者多个列进行排序的数据结构
  • 索引能够大幅度提升检索速度
  • 创建、更新索引本身也会好烦空间和时间

索引类型

  • 普通索引(create index)
  • 唯一索引,索引列的值必须唯一(create unique index)
  • 多列索引
  • 全文索引(用到比较少)

什么时候创建索引

建表的时候需要根据查询需求来创建索引:

  • 经常用作查询条件的字段(where 条件)
  • 经常用作表连接的字段
  • 经常出现 order by,group by 之后的字段

创建索引需要注意的

  • 非空字典 not null,MySQL 航南对空值作查询优化
  • 区分度高,离散度大,作为索引的字段值尽量不要有大量相同值
  • 索引的长度不要太长(耗费时间)

什么时候索引会失效

记忆口诀:模糊匹配、类型隐转、最左匹配

  • 以 % 开头的 like 语句,模糊匹配
  • 出现隐式类转换(在 Python 这种动态语言查询中需要注意)
  • 没有满足最左前缀元组(startswith)

聚集索引和非聚集索引

  • 聚集还是非聚集指的是 B+Tree 叶子节点存的是指针还是数据记录
  • MyISAM 索引和数据分离,使用的是非聚集索引
  • InnoDB 数据文件就是索引文件,主键索引就是聚集索引
image
image
image

非聚集和聚集索引的文件存储方方式

# 非聚集
create table myisam_table(
'id' integer primary key,
 title varchar(80)
) engine = MYISAM;

# 聚集
create table innodb_table(
'id' integer primary key,
'url_md5' char(32),
key 'index_url' ('url_md5')
) engine = InnoDB;

如何消除慢查询

所谓馒查询即查询时间较慢的 SQL 语句,在高并发时影响很大,因此我们要尝试去消除慢查询:

  • 原因:慢查询通常是缺少索引,索引不合理或者业务代码实现导致的
  • slow_query_log_file:开启慢查询日志,通过工具分析慢查询的 SQL 语句
  • 通过 explain 排查索引问题
  • 调整数据修改索引,业务代码逻辑限制不合理访问

11. MySQL 连接

MySQL 表之间连接大致分为以下几类:

  • 内连接(inner join):两个表都存在匹配是,才会返回匹配行
  • 外连接(left/right join):返回一个表的行,即使另一个没有匹配
  • 全连接(full join):只要某一个表存在匹配就返回

示例数据表:

# A 表               # B 表
id  val             id  val 
1   ab              1   ab
2   a               3   b

内连接

将左表和游标能够关联的数据连接后返回,类似于 "交集"。

语法:

select * from A inner join B on a.id = b.id

mysql> select * from A inner join B on a.id=b.id;
+----+------+----+------+
| id | val  | id | val  |
+----+------+----+------+
|  1 | ab   |  1 | ab   |
+----+------+----+------+

外连接

  • 左连接返回左表中所有记录,即使右表中没有匹配的记录
  • 右连接返回左表中所有记录,即使左表中没有匹配的记录
  • 没有匹配的字段会设置为 null

语法:

select * from A left join B on a.id = b.id
select * from A right join B on a.id = b.id
mysql> select * from A left join B on a.id=b.id;
+----+------+------+------+
| id | val  | id   | val  |
+----+------+------+------+
|  1 | ab   |    1 | ab   |
|  2 | a    | NULL | NULL |
+----+------+------+------+
2 rows in set (0.00 sec)

mysql> select * from A right join B on a.id=b.id;
+------+------+----+------+
| id   | val  | id | val  |
+------+------+----+------+
|    1 | ab   |  1 | ab   |
| NULL | NULL |  3 | b    |
+------+------+----+------+
2 rows in set (0.00 sec)

思考题

  • 为什么 MySQL 数据库的主键使用自增的整数比较好?
  • 使用 uuid 可以吗?为什么?
  • 如果是分布式系统下我们生成数据库的自增 id 呢?

12. Redis 数据库

12.1 Redis 常考题

什么是缓存,为什么要用缓存

缓存有数据库缓存、文件缓存以及内存缓存,内存缓存速度最快,也是最常用的。常见的内存缓存有:Redis 和 Memcached。

  • 缓解关系型数据库并发访问压力:热点数据(经常被访问的)

  • 减少响应时间:内存 IO 速度比磁盘快

  • 提升吞吐量:Redis 等内存数据库单击就可以支撑很大并发

计算机各种操作时间对比:

操作 响应时间 操作 响应时间
打开一个网站 几秒 在数据库查询一条记录(有索引) 十几毫秒
机械磁盘一次寻址定位 4 毫秒 从机械磁盘顺序读取1MB数据 2毫秒
从SSD磁盘顺序读取1MB数据 0.3毫秒 从远程分布缓存Redis读取一个数据 0.5毫秒
从内存中读取1MB数据 十几微秒 Java程序本地方法调用 几微秒
网络传输2KB数据 1微秒

Redis 和 Memcached 主要区别

image

Redis 常用数据类型和使用场景

  • String(字符串):用来实现简单的 kv 键值对存储,如:计数器

  • List(链表):实现双向链表,如:用户的关注,粉丝列表

  • Hash(哈希表):用来存储彼此相关信息的键值对

  • Set(集合):存储不重复元素,如:用户的关注者

  • Sorted Set(有序集合—):实时信息排行榜

12.2 Redis 内置实现

对应中高级工程师,需要了解 Redis 各种类型的 C 底层实现方式

  • String:整数或者 sds(Simple Dynamic String)
  • List:ziplist 或 double linked list,ziplist(通过一个连续的内存块实现 list 结构,其中的每个 entry 节点头部保存前后节点长度信息,实现双向链表功能)
  • Hash:ziplist 或者 hashtable
  • Set:intset 或 hashtable
  • SortedSet:skiplist 跳跃表

深入学习参考书籍:《Redis 设计与实现》

跳跃表结构

image

12.3 Redis 持久化

Redis 支持两种方法实现持久化:

  • 快照方式:把数据放在磁盘二进制文件中 dump.rdb(快照的实现方法是指定时间间隔把 Redis 数据库状态保存到一个压缩的二进制文件中)

  • AOF(Append Only File):每一个写入命令追加到 appendonly.aof 中(缺点:太废空间)

  • 可以通过修改 Redis 配置实现

12.4 Redis 事务

Redis 事务和 MySQL 有什么不同:

  • 将多个请求打包,一次性、按序执行多个命令的机制
  • Redis 通过 MULTI、EXEC、WATCH 等命令实现事务功能
  • Python redis-py pipeline=conn.pipeline(transaction=True)

12.5 Redis 分布式锁

如何实现分布式锁:

  • 使用 setnx 实现加锁,可以同时通过 expire 添加超时时间
  • 锁的 value 值可以使用一个随机的 uuid 或者特定的命名
  • 释放锁的时候,通过 uuid 判断是否是该锁,是则执行 delete 释放锁

12.6 缓存模式

常用的缓存使用模式:

  • Cache Aside:同时更新缓存和数据库
  • Read/Write Through:先更新缓存,缓存负责同步更新数据库
  • Write Behind Caching:先更新缓存,缓存定期异步更新数据库

先更新缓存再更新数据库,并发写入操作可能导致缓存读取的是 脏数据

12.7 如何解决缓存穿透问题

(必然不存在的数据)大量查询不到的数据的请求落到后端数据库,数据库压力增大就会导致缓存穿透,有可能被黑客攻击。

  • 由于大量缓存查不多就去数据库取,数据库也没用要查的数据
  • 解决:对于没查找的返回为 None 的数据页缓存
  • 插入数据的时候删除相应缓存,或者设置较短的超时时间
  • 互斥锁、异步构建缓存、布隆过滤器

https://blog.csdn.net/hjm4702192/article/details/80518952

12.8 如何解决缓存击穿问题

是指某个热点的 key 在过期瞬间有大量请求访问这个 key,缓存里没有就去数据库中查找,从而增加数据库压力,导致缓存被击穿。

  • 热点数据 key 失误导致大量请求打到数据库增加数据库压力
  • 分布式锁:获取锁的线程从数据库拉数据更新缓存,其他线程等待
  • 异步后台更新:后台任务针对过期的 key 自动刷新

12.9 如何解决缓存雪崩问题

缓存不可用或者大量缓存 key 同时失效,大量请求直接打到数据库

  • 多久缓存:不同级别的 key 设置不同的超时时间
  • 随机超时:key 的超时时间随机设置,防止同时超时
  • 架构层:提升系统可用性,监控、报警完善

Redis 应用

Redis 的另一种应用就是实现分布式锁

  • 请你基于 Redis 编写代码实现一个简单的分布式锁
  • 要求:支持超时时间参数
  • 深入思考:如果 Redis 单个节点宕机了,如何处理?还有其他业界的方案实现分布式锁么?

分布式锁:https://blog.csdn.net/t8116189520/article/details/91383256

13. 死锁

死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。当多个事务试图以不同的顺序锁定资源时,就有可能产生死锁。多个事务同时锁定同一个资源时也会产生死锁。

# 事务一
start transaction;
update stockprice set close = 45.5 where stock_id = 4 and date = '2002-05-01';
update stockprice set close = 19.8 where stock_id = 3 and date = '2002-05-02';
commit;

# 事务二
start transaction;
update stockprice set high = 20.12 where stock_id = 3 and date = '2002-05-02';
update stockprice set close = 47.20 where stock_id = 4 and date = '2002-05-01';
commit;

若以上两个事务都执行了第一条 update 语句,更新了一行数据,同时也锁定了该行数据,接着去尝试执行第二条 update 语句时,发现该行已被对方锁定,然后两个事务都等待对方释放锁,同时又持有对方需要的锁,则陷入死循环。除非有外力才有可能解除死锁

解决方法

死锁发生后,只有部分或完全回滚其中一个事务,才能打破死锁,大多数情况下只需重新执行因死锁回滚的事务即可。

InnoDB 目前除了死锁的方法:将持有最少行级排他锁的事务进行回滚(这是相对比较简单的死锁回滚算法)。

参考文章:https://www.cnblogs.com/hhthtt/p/10707541.html

14. 并发控制

多个查询在同一时刻修改数据,都会产生并发控制。

如果两个进程同时对某张数据表进行操作,A 进程要读取数据表中的记录,而 B 进程要修改记录,那么两种就有可能会发生冲突。就有可能导致数据损坏,为了避免这种情况发生,就要通过设置锁(lock)来防止数据损坏。

如果 A 进程在读取数据,B 进程想修改数据,而数据表已被 A 进程锁定了。就必须等待,释放锁后才能继续修改。

14.1 读写锁/共享(排他)

多个用户在同一时刻并发读取数据一般不会有问题,但如果某个客户在读取,同时另一个用户试图删除。读的用户就有可能报错退出,也有可能读取到不一致的数据,为了安全起见,即使读取数据也需要注意。

解决这类问题就是使用并发控制,即在处理并发读或写时,可以通过实现一个由两种类型的锁组成的锁系统来解决问题。这两种类型的锁通常被称为 共享锁(shared lock)和 排他锁(exclusive lock),页脚读锁(read lock)和写锁(write lock)

读锁

共享的,相互不阻塞,多个客户在同一时刻可以同时读取同一个资源,互不干扰。

写锁

排他的,一个写锁会阻塞其他的写锁和读锁,这样出于安全策略的考虑。

14.2 锁粒度

加锁也会耗费资源,锁的各种操作,如:获得锁、检查锁是否被解除、释放锁等,都会增加系统的开销。一旦有很多锁的时候,必然要占用大量的系统资源。因此就需要一种理想的 锁策略,只对会修改的数据片进行精确锁定。在给定的资源上,锁定的数据量越小,则系统的并发程度越高,只要相互之间不发生冲突即可。

锁策略

在锁的开销和数据的安全性之间寻求平衡,这种平衡也会影响到性能。大多数商业数据库没有更多的选择,而是在表上添加 行级锁(row level lock),并以各种复杂的方式来实现,以便在锁比较多的情况下尽可能地提供更好的性能。

而 MySQL 有多种选择,基本上每种引擎都可以实现自己的锁策略和锁粒度,将锁粒度固定在某个基本,可以为某些特定的应用场景提供更好的性能,但是也会失去对另外一些应用场景的良好支持。

两种常用的锁策略:表锁、行级锁

表锁(table lock)

MySQL 最基本的锁策略,开销最小,它会锁定整张表。一个用户要对表进行写操作(插入、删除、更新等),就先要获得写锁(会阻塞读操作)。只有当没有写锁时,其他用户才能获取读锁,读锁之间相互不阻塞。

写锁等级高于读锁,因此写锁请求可能会被插入到读锁队列前面。

行级锁(row lock)

可以最大程度支持并发处理(也会带来最大的锁开销)。

行级锁只在存储引擎层实现,而 MySQL 服务器层没有实现,所有的存储引擎都以自己的方式实现了锁机制。

MySQL 逻辑架构图

image

15. 为什么使用 B+ 树作为索引结构

一般来说,索引本身也很大,不可能全部存储在内存中,往往是以索引文件的形式存储的磁盘上。但是这样的话,索引查找就必然要产生磁盘 I/O 消耗,相对于内存读取速度,磁盘 I/O 读取速度要慢很多。因此衡量一个索引优劣的指标就是要尽量减少查找过程中磁盘 I/O 的存取次数。

B+Tree 结构可以在读取相同磁盘块同时,尽可能多的加载索引结构,来提高索引命中效率,从而达到减少磁盘 I/O 的读取次数。

B-Tree 结构

image

利用了磁盘块特性进行构建的数,每个磁盘块一个节点,每个节点包含了很多关键字。把树的节点关键字增多后树的层级比原来的二叉树少了,减少了数据查找的次数和复杂度。

利用了磁盘预读原理,将每一个节点的大小设为等于一个页(每页 4K),这样每个节点只需一次 I/O 就可以完全载入。

B+Tree 结构

[图片上传失败...(image-122421-1593095890096)]

B-Tree 的变种,数据只存储在叶子节点。这样在 B-Tree 基础上每个节点存储的关键字树更多,树的层级更少,所有查询更快。所有关键字指针都存在叶子节点,所以每次查找的次数相同,查询速度也更稳定。

B-Tree 检索一次最多需要访问 h(树的深度)个节点,检索一次最多需要 h-1 次 I/O 操作(根节点常驻内存),渐进复杂度为 O(h)。一般在实际应用中,出度 d(节点最大的分支数)是非常大的数字,通常超过 100,因此 h 非常小(通常不超过 3)。

B+Tree 更适合所有,原因在于内节点出度 d 有关,d 越大索引的性能越好,而出度的上限取决于节点内 key 和 data 的大小。

B-Tree 和 B+Tree 对比

  • B-Tree:节点中存储 key 和 data 以及指针,且相互间隔,同一个节点,key 增序。每个非叶子节点由 n-1 个key 和 n 个指针组成,其中 d<=n<=2d。
  • B+Tree:内节点不存储data,只存储 key 和指针,叶子节点不存储指针,存储 key 和data。因为节点内部没有 data,所以可以放更多的 key,也就意味着树的深度越小,出度越大。

树的深度越大检索效率越低

参考文章:

  • https://www.jianshu.com/p/4dbbaaa200c4

16. MySQL 数据库的优化?

1、SQL 语句优化

  • 慢查询定位性能瓶颈,explain 查询和分析 SQL 语句的执行计划

  • count:使用索引,避免使用 select count(*) from tb_name where xx、max:添加索引

  • 子查询优化:通常优化为 join 查询(优化时需注意一对多关联,数据重复等)

  • group by:结合子查询和索引

  • limit:使用有索引的列或记录上一次返回的主键,在下次查询时使用主键过滤

2、索引优化

  • 在 where、group by、order by、on 从句中出现的列,字段越小越好,离散度大的列放在联合索引前面

  • 避免重复索引或冗余索引,pt-duplicate-key-checker 工具查询重复或冗余索引

3、数据库表结构优化

  • 选择合适的数据类型,存下数据的最小数据类型
  • int 要比 varchar 更小,可以用来存储日期时间,bigint 存储 ip 地址
  • 尽可能使用 not null 定义字段
  • 尽量少用 text 类型

4、系统配置优化

  • 增加 tcp 支持的队列树
  • 减少断开连接,资源回收
  • 多个配置文件,后面的会覆盖前面的

5、服务器硬件优化

  • 选择合适的 CPU,MySQL 对 cpu 核数支持并不是越多越好
  • 磁盘 辍 优化

6、事务、锁定表

7、通过 explain 查询和分析 SQL 的执行计划

image

explain 返回的每列的含义

  • table:显示这一行的数据时关于哪张表的
  • type:这是重要的列,显示连接使用了何种类型。从最好到最差的连接类型为 const、eq_reg、ref、range、index 和 ALL
  • possible_keys:显示可能应用在这种表中的索引。如果为空,没有可能的索引
  • key:实际使用的索引,NULL 没有无索引
  • key_len:索引长度,不损失精度前提下,长度越短越好
  • ref:MySQL 任务必须检查的用来返回请求数据的行数
  • rows:MySQL 认为必须检查的用来返回请求数据的行数
  • extral:通常用三个返回值(NULL、Using filesort、Using temporary)

1、Using filesort

使用文件排序的方式排序,当返回值是这个时,查询就需要优化了。表示 MySQL 需要进行额外的步骤来发现如何对返回的行排序,它根据连接类型以及存储排序键值和匹配条件的全部的行指针来排序全部行。

2、Using temporary

使用 临时表方式,当返回值为这个时,查询就需要优化了。MySQL 需要创建一个临时表来存储结果,通常发在对不同的列集进行 order by 上,而不是 group by 上。

17. 如何定位查询瓶颈以及优化查询

MySQL提供了慢查询可以快速定位性能瓶颈

# 查看慢查询日志是否开启,off 为关闭
show variables like 'slow_query_log';

# 查看 log_queries_not_using_indexes 是否开启
show variables like '%log%';

# 开启 log_queries_not_using_indexes
set global log_queries_not_using_indexes=on;

# 开启慢查询日志
set global slow_query_log=on;

# 查看慢查日志记录时间,0 表示时刻都会记录,生产环境中不能为 0
show variables like 'long_query_time';

# 设置查询记录的时间(即查询超过 0s 的SQL语句都会被记录在慢查日志中)
set global long_query_time=0;

# 查看慢查询日志存储位置
show variables like 'slow_query_log_file';

# 指定慢查询日志存储位置
set global show_query_log_file='/var/lib/mysql/homestead-slow.log';

# 记录没有使用索引的sql
set global log_queries_not_using_indexes=on;

慢查日志分析工具

  • mysqldumpslow(官方)
  • pt-query-digest 第三方工具

18. 面试题

1、MySQL锁有几种;死锁是怎么产生的,为何,以及如何分区、分表

数据库锁定机制简单来说,就是数据库为了保证数据的一致性,而使各种共享资源在被并发访问变得有序所设计的一种规则。

常见的MYSQL锁有三种级别:页级、表级、行级

  • 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
  • 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
  • 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之。

所谓死锁: 是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去.此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程.表级锁不会产生死锁.所以解决死锁主要还是针对于最常用的InnoDB。

https://blog.csdn.net/q1925387431/article/details/86612581

https://blog.csdn.net/weixin_42358062/article/details/80731501

2、MySQL的char varchar text的区别

  • char:长度固定,每条数据占用等长字节空间,适合用在身份证、手机号码上

  • varchar:可变长度,可设置长度,适合用在长度可变的属性

  • text:不设置长度,当不知道长度时可用 text,不区分大小写

    查询速度:char > varchar > text

    https://blog.csdn.net/brycegao321/article/details/78038272

3、了解join么,有几种,有何区别,A LEFT JOIN B,查询的结果中,B没有的那部分是如何显示的(NULL)

三种,

  • inner join:内连接,显示左表及右表所有符合条件的记录(即交集)
  • left join、right join:外连接,left join(显示左表的全部记录及右表符合条件的记录)、right join(显示右表所有记录及左表符合条件的记录)
  • full join:全连接,即显示左右表所有记录,不常用

B 没有的部分以 NUll 形式显示。

4、索引类型有几种,BTree索引和hash索引的区别

索引可以大大提高MySQL的检索速度

  • 普通索引:
  • 唯一索引:
  • 全文索引:
  • 多列索引:
  • hash 索引:效率高于 B-Tree,仅仅能满足"=","IN"和"<=>"查询,不能使用范围查询,无法被用来避免数据的排序操作,遇到大量Hash值相等的情况后性能并不一定就会比B-Tree索引高
  • B-Tree 索引:需要从根节点到枝节点(链表实现的树结构),所有关键字都出现在叶子结点的链表中(稠密索引),且链表中的关键字恰好是有序的,不可能在非叶子结点存数据,必须从索引的最左边的列开始。

5、手写:如何对查询命令进行优化

# 尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引
# 尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
select id from t where num is null
# 可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
select id from t where num=0

# 尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描

# 尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描
select id from t where num=10 or num=20
select id from t where num=10 union all select id from t where num=20

# 字段按需提取,尽量少用 select *,而是要按照提取字段提取
select a, b from t

# 尽量使用exists代替select count(*) 来判断是否存在记录。优化器优化exists谓词时支持短路功能。只要找到一行,不需要再扫描其他行就可以确定该表是否包涵行了。count函数只有在统计表中所有行的行数时使用。

# 尽量使用(not) exists代替(not) in 操作,in的sql性能总是比较低的。

# 尽量使用“>=“,不用使用”>“

# 在where 子句中,任何对列的操作(函数、计算等)讲导致索引失效,这些操作应该尽可能地移至等号右边,如where substring(id,1,1)=‘a‘,应该写成where id like 'a%‘;where result*10> 30应该写成where result >30;

# 尽量使用not in,可以用left outer join代替它。

https://www.cnblogs.com/Mryang-blog-cn/p/SQLYOUHUA.html

6、NoSQL了解么,和关系数据库的区别;redis有几种常用存储类型

NoSQL(not only SQL),非关系型数据库,数据表之间没有相应关系,以键值对形式存储(存储方式不同)。对事物支持不怎么好,查询方式不同。

redis 常用存储类型:list、set、有序集合、str、hash

https://www.cnblogs.com/sunzhiqi/p/10869655.html

7、redis、memcached

1、Redis

NoSQL (Not Only SQL)数据库即非关系型数据库,与关系型数据库区别:

  • 表与表之间没有关联
  • 不支持 SQL 语法,不支持事务
  • 不擅长关系特别复杂的数据查询,但是速度快
  • 以 key-value 形式存储数据
  • NoSQL 中没有通用的语言,每种数据库都有自己的语法,以及擅长的业务场景

常见的 NoSQL有:Redis、MongoDB、Hbase hadoop 以及 Cassandra hadoop 等

优势

  • 性能极高:Redis能读的速度是110000次/s,写的速度是81000次/s 。
  • 丰富的数据类型:Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
  • 原子 : Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
  • 丰富的特性: Redis还支持 publish/subscribe, 通知, key 过期等等特性。

常用数据类型:str、hash、set、有序集合、列表

应用场景:做缓存


2、memcached

Memcached 是一个自由开源的,高性能,分布式内存对象缓存系统。Django 原生支持的最快最有效的缓存系统。对于大多数场景,我们推荐使用Memcached,数据缓存在服务器端。

8、为什么要使用缓存

在 Django 项目中,当用户请求达到视图后,视图会先从数据库中提取数据,然后将其放在模板中渲染。若每次都从数据库中提取数据,必然会导致网站性能降低。不仅服务器压力大,而且客户端也无法及时获得响应。

如果能将渲染后的结果放在速度更快的介质中,每次请求来时,先从这个介质中检查是否有对应的资源。若有直接取出响应即可,大大提高了网站的性能,节省了数据和渲染的时间,而且能提高用户体验。

9、缓存应用场景

缓存主要适用于对页面实时性要求不高的页面。存放在缓存的数据,通常是频繁访问的,而不会经常修改的数据。如:

  • 博客文章:假设用户一天更新一篇文章,那么可以为博客设置1天的缓存,一天后会刷新。
  • 购物网站:商品的描述信息几乎不会变化,而商品的购买数量需要根据用户情况实时更新。我们可以只选择缓存商品描述信息。
  • 缓存网页片段:比如缓存网页导航菜单和脚部(Footer)

像实时监控股票走势、实时显示网站访问量这种需要实时刷新的数据,就不适合用缓存,需要从数据库中立马提取数据。

10、MySQL 常见数据库引擎及比较?

  • InnoDB:支持事物、外键、奔溃修复能力和并发控制。若对事物的完整性要求比较高(如银行),要求实现并发控制(如售票),选择 InnoDB 有很大的优势。若需要频繁的更新、删除操作的数据库,也可选择 InnoDB,因为它支持事物的提交和回滚。
  • MyISAM:插入数据库,空间和内存使用较低。若表主要用于插入新纪录和读出记录,选择 MyISAM 能实现处理高效率。若应用的完整性、并发性要求较低,也可以使用。
  • MEMORY:所有数据都在内存中,数据处理速度快,安全性不高。若需要很快的读写速度,对数据安全性要求较低,可选择。它对表的大小有要求,不能建立太大的表,所以只适用于相对较小的数据库表。

注意:同一数据库可使用多种存储引擎,若对表要求比较高的事务处理,可选择 InnoDB;若对查询要求较高的表,可选择 MyISAM;若该数据库需要一个用于查询的临时表,可选择 MEMORY

11、什么是事务?MySQL 如何支持事务?

事务是由一步或几步数据库操作序列组成的逻辑执行单元,这系列操作要么全部执行,要么全部放弃执行。

程序与事务是两个不同的概念,一般程序中可能包含多个事务。

事务的 ACID 特性:

  • 原子性:事务是应用中最小的执行单位,不可再分的最小逻辑执行体。
  • 一致性:事务执行的结果,必须使数据库从一个一致性状态,变为另一个一致性状态。当数据库中只包含事务成功提交的结果时,数据库处于一致性状态,一致性是通过原子性来保证的。
  • 隔离性:各个事务的执行互不干扰,任意一个事务的内部操作对其他并发的事务,都是隔离的
  • 持续性:持久性,事务一旦提交,对数据所做的任何改变,都要记录到永久存储器中,通常是保存到物理数据库

MySQL 事务处理两者方法:

  • begin、rollback、commit 来实现
    • begin 开始一个事务
    • rollback 事务回滚
    • commit:提交事务
  • 直接用 set 来改变 MySQL 自动提交模式
    • MySQL 默认自动提交,可通过设置参数来禁止或开启事务的提交
    • set autocommit=0:禁止自动提交
    • set autocommit=1:开启自动提交

12、主键和外键的区别?

  • 主键:定义一个表中起主要作用的数据项,在表中唯一的,为表建立索引
  • 外键:定义一个表中某数据项的数据,要参照另一个表的主键数据,既没有在另一个表的主键数据中出现的数据,不允许在这个表的外键数据项中出现。

13、MySQL 常见函数?

sum、abs、floor

14、列举创建索引但无法命中索引的几种情况

  • 若条件中有 or,即使其中有条件带有索引也不会使用(尽量少使用 or),要想使用 or,又想使索引生效,只能将 or 条件中每个列都加上索引
  • 对于多列索引,不是使用的第一部分,则不会使用索引
  • like 查询以 % 开头
  • 若列类型为字符串,那一定要在条件中奖数据使用引号引用起来,否则不使用索引
  • MySQL 估计使用全表扫描要比使用索引快,则不使用索引

15、如何开启慢查询日志?

  • 修改 MySQL 配置文件
  • 通过 set global 语句来实现

16、数据库优化方案?

  • 索引优化
  • 合理的数据库设计
  • 系统配置及硬件优化
  • 代码优化
  • 定位慢查询并优化
  • 合理使用索引
  • 分表
  • 读写分离
  • 缓存

17、简述 MySQL 执行计划?

MySQL 数据库中,在 select 查询语句前加上 EXPLANDESC 关键字,即可查看该查询语句的执行计划,分析执行计划是优化慢查询的重要手段。

18、简述数据库读写分离?

前提:主备两台服务器同步数据

利用数据库的主从分离:主(用于增加、删除、更新),从(用于查询)。

你可能感兴趣的:(数据库(五))