一、数据库基础知识
1、请你说一说数据库的三大范式:
2、数据库事务以及四个特性ACID、事务隔离级别、脏读、不可重复读和幻读
a、事务的四大基本特性ACID(即原子性、一致性、隔离性、持久性)。
- 1、A (Atomicity) 原子性:所有操作要么全部成功,要么全部失败回滚。只要有一个操作失败,整个事务就失败,需要回滚。
- 2、C (Consistency) 一致性:事务开始前和结束后,数据库的完整性约束没有被破坏。
Eg:如A向B转账,不可能A扣了钱,B却没收到。 - 3、I (Isolation) 独立性:又叫隔离性,当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
比如现在有个交易是从A账户转100元至B账户,在这个交易还未完成的情况下,
如果此时B查询自己的账户,是看不到新增加的100元的。 - 4、D (Durability) 持久性:持久性是指一旦事务提交后,它所做的修改将会永久的保存在数据库上,即使出现宕机也不会丢失。
b、事务的隔离级别
从理论上来说, 事务应该彼此完全隔离, 以避免并发事务所导致的问题,然而, 那样会对性能产生极大的影响, 因为事务必须按顺序运行, 在实际开发中, 为了提升性能, 事务会以较低的隔离级别运行,事务的隔离级别可以通过隔离事务属性可分为四个级别。读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )
读未提交(read uncommitted)一个事务还没提交时,它做的变更就能被别的事务看到。
读提交(read committed) 一个事务提交之后,它做的变更才会被其他事务看到。Oracle, SQL Server默认隔
可重复读(repeatable read):一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的(会有幻读现象)。MySQL默认的隔离级别
串行化(serializable):最高的隔离级别,在这个隔离级别下,不会产生任何异常。并发的事务,就像事务是在一个个按照顺序执行一样。对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
- (1)脏读:事务A读取了事务B还未提交的数据。
- (2)不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,读到的数据是不同的。
- (3)幻读:幻读解决了不重复读,保证了同一个事务里,查询的结果都是事务开始时的状态(一致性)。
例如:事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作 这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。 而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有跟没有修改一样,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。
小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。
3、数据库索引,索引的底层使用的是什么数据结构(B+树),多加索引一定会好吗?最左前缀原则。 索引的分类:聚簇索引、哈希索引、覆盖索引
• 索引是一种帮助MySQL高效获取数据的排好序的数据结构,解决查询语句中where和order by部分的性能。
• 索引也是一张表,大大提高了查询速度的同时降低了更新表的速度,如对表进行INSERT、UPDATE、DELETE。
4、主从同步的原理:
- mysql主从同步(实际场景:双主方案、一主多从方案)
- master(主库)在每次准备提交事务完成数据更新前,将改变记录到二进制日志(binlog)中
- slave(从库)发起连接,连接到master,请求获取指定位置的binlog文件
- master创建【dump thread】,推送binlog的slave
- slave启动一个【I/O thread】来读取主库上binlog中的事件,并记录到slave自己的中继日志(relay log)中
- slave还会起动一个【SQL thread】,该线程从relay log中读取事件并在备库执行,完成数据同步
- slave记录自己的binlog
- redis主从同步的原理:
sequenceDiagram
participant Master as 主库(Master)
participant Slave as 从库(Slave)
Note over Slave: 1. 建立连接
Slave->>Master: SLAVEOF <master_ip> <port>
Note over Master: 2. 全量同步(RDB)
Master->>Master: 执行BGSAVE生成RDB快照
Master->>Slave: 发送RDB文件
Slave->>Slave: 清空旧数据,加载RDB
Note over Master: 3. 增量同步(命令传播)
loop 持续同步
Master->>Master: 接收写命令
Master->>Master: 存入复制积压缓冲区(repl_backlog)
Master->>Slave: 异步发送写命令
Slave->>Slave: 执行接收到的命令
end
Note over Slave: 4. 断线重连
alt 偏移量在backlog内
Slave->>Master: PSYNC <runid> <offset>
Master-->>Slave: +CONTINUE (增量同步)
else 偏移量不在backlog内
Slave->>Master: PSYNC <runid> <offset>
Master-->>Slave: +FULLRESYNC (触发全量同步)
end
5、数据库中的WAL技术
WAL(Write-Ahead Logging,预写式日志)是现代数据库系统中确保数据一致性和持久性的核心技术
基本原则:
- 数据写入前必须先记录日志, 只有日志写入成功后,修改才能应用到实现数据文件。
graph LR
A[事务开始] --> B[生成日志记录]
B --> C[日志写入磁盘]
C --> D[修改内存数据]
D --> E[事务提交]
WAL技术通过精巧的日志机制,在性能与可靠性之间取得平衡,是数据库系统的基石技术之一。不同数据库的实现各有特点,但核心思想高度一致。
6、MVCC机制
MVCC(Multi-Version Concurrency Control)是现代数据库系统实现高并发访问的核心技术.
通过数据多版本化解决读写冲突,显著提升系统并发性能。
特点:
- 高并发: 读写都通过版本号控制,事务处理时不会出现冲突。
- 避免锁竞争: 多版本号可以避免锁竞争。减少死锁概率。
- 原则:"读不阻塞写,写不阻塞读"
mysql
1、常用操作
mysql -u<username> -p<passward> # 登陆
show databases; # 查看数据库
use dbName; # 切换数据库
show tables; # 查看表
select * from dbName.user; # 查看表的数据
insert into dbName.user values(1,'张三',18); # 插入数据
update dbName.user set name='李四' where id=1; # 更新数据
delete from dbName.user where id=1; # 删除数据
drop table dbName.user; # 删除表
create table user (
id int not null auto_increment primary key,
name varchar(16),
age int default 19
)engine=innodb default charset=utf8; # 创建数据库表
select val1,val2 from dbName.tableName where [条件]; # 查询数据
# 关于条件:1. 支持比较运算符:=、<>、>、<、>=、<=、in、between。
# 2. 模糊查询 like, eg: like '%张%' %表示任意多个任意字符,_表示一个任意字符
# 3. 判空:is null、is not null
# 聚合查询函数: count(*)、max(id)、sum(id)、min(id)、avg(id)
# 按某个字段排序:order by id asc/desc (升序/降序)
# 按某个字段分组:group by id
# 按某个字段分页:limit start,count 开始位置 条数
2、sql查询句的执行顺序:
执行顺序解释
1.from 要做数据分析,得先有个表
2.join 一个表可能还不够,两个表甚至多个表都可以,关联条件啥也先不用,可以都来个 笛卡儿积先。
3.on 在诸多表左右连接后,设定两个表之间的关联键,把不符合条件的全部筛掉
4.where 上三步整合各表,形成一个统一大表;在此大表上,设置筛选条件
5.group by 把指定字段相同的行组合在一起,其余没有加入group by的字段,可以用聚合函数如max/min等合并
6.having 在group by了之后,再度指定筛选条件;注意where和having是不同的,主要在于中间多了group by
7.select 在行层面的处理暂告一段落,在列层面再来一波
8.distinct 指定字段去重
9.order by 指定字段排序,升降序
3、MySQL的存储引擎: InnoDB 存储引擎(B+树实现)、 MyISAM
存储引擎层负责数据的存储和提取。
• InnoDB:支持事务处理,支持外键,支持崩溃修复能力和并发控制。如果对事物的完整性要求比较高(如银行),要求实现并发控制(如售票),那么选择InnoDB有很大的优势。如果需要频繁更新、delete数据,也可以选择InnoDB,因为InnoDB支持事物的提交commit和回滚rollback.
• MyISAM:插入数据快,空间和内存使用比较低。如果表主要用于插入新记录和读出记录,那么选择MyISAM能实现处理高效。如果应用的完整性,并发性要求比较低,也可以使用。
• MEMORY:所以数据都在内存中,数据处理速度快,但安全性差。它对表有要求,不能建立太大的表。只能应用于小表,对速度要求高,对安全性要求很低的场景。
InnoDB->聚集索引:数据和B+Tree的节点存储在一起;
底层的一张表只有2个文件存储。.frm 表结构、.ibd文件:存放数据库表数据和索引。
MyISAM->非聚集索引:数据和B+Tree的节点分开存储。
底层的一张表有3个文件存储。.frm 表结构、.myd 即 my data,表数据文件、.myi 即 my index,索引文件
redis
1、Redis的key支持的数据结构
- string,list,set,zset,hash
- 另外几种数据结构 HyperLogLog、Geo、Pub/Sub。
- 更高级的数据结构: Redis Module,像 BloomFilter,RedisSearch,Redis-ML。
2、Redis事务
Redis 的所有操作都是原子性的,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过 MULTI 和 EXEC指令包起来。MULTI、EXEC、DISCARD、WATCH。
1、Redis通过四个命令实现事务:WATCH,MULTI,EXEC,DISCARD
2、Redis事务以MULTI命令开始,EXEC/DISCARD命令结束。EXEC用于提交事务,DISCARD用于取消事务。
3、在开启事务时,Redis操作命令并不会立即执行,而是会先进入事务队列。Redis事务队列是一个FIFO队列
4、当执行EXEC命令时,会把事务队列中的命令逐个执行,不会被任何命令中断。
5、Redis中的Watch机制,在开启事务前,使用WATCH命令监视指定的key,形成乐观锁。事务执行时,如果被WATCH的key发生了变化,则事务失效,不能执行。
Redis事务时不支持回滚的。也就是说,如果事务中的命令执行出错,已经执行的命令不会撤回,后续的命令也会继续执行。Redis事务的命令分为入队和执行两个阶段。在入队(进入事务队列)时出错,Redis会忽略入队出错的命令,不会影响后续命令入队。执行时出错,出错命令会被Redis忽略,其他没有出错的命令均会执行。
6、Redis事务不能嵌套。如果再收到MULTI命令开启事务,直接返回错误。遇到嵌套事务返回错误后,并不会影响事务队列继续接收命令,也不会修改事务队列中的任何数据。
多个客户端同时开启事务会怎样?
在并发情况,很容易出现多个客户端同时开启事务的情况。这时Redis会区分客户端维护事务队列吗?另外,实际工程中为了提升性能,通常会用Redis连接池复用连接,这种情况下,会出现连接复用导致事务嵌套吗?
Redis事务队列是与连接绑定的,不同的连接中允许同时开启事务,不会相互影响。
合理的事务管理器中,会在开启事务时绑定连接,防止连接被其他请求复用。不过应用不当,会出现连接不能被归还的问题。(由此可见,一个优秀的事务管理器是多么重要)
Redis中的事务
- 开启事务
MULTI:开启事务,事务块中多条语句会按照顺序放入队列当中,最后由EXEC来执行
MULTI
INCT key
INCR key
INCR key
PING
GET key
执行事务
EXEC: 执行事务块中的命令监视一个或者多个key
WATCH: 监视一个或者多个key,如果事务在执行之前这个key被其他命令改动,事务就会被打断
UNWATCH: 取消WATCH对所有的key的监视取消事务
DISCARD: 取消事务
MULTI
SET name hello
INCR counter4
INCR COUNTER5
DISCARD
- 事务中的错误处理
语法错误: 命令不存在,参数错误
如果有语法错误,Redis在EXEC后直接返回错误,正确的命令也不会被执行
运行错误:指在运行命令的时候出现的问题,错误的不会被执行,正确的会被执行
MULTI
SET 1test2 1
SADD test2 2 # 集合操作字符串
SET test2 3
EXEC
3、redis streams 消息缓存
支持 publish/subscribe
按 key 设置过期时间,过期后将会自动删除。
4、持久化机制:RDB/AOF
1、RDB:是指用数据集以快照的方式将数据写入一个临时文件,待系统恢复正常的时候,用这个临时文件替换上次持久化的文件,达到数据恢复。
优点:
(1)只有一个文件 dump.rdb,方便持久化。
(2)容灾性好,一个文件可以保存到安全的磁盘。
(3)性能最大化,fork 子进程来完成写操作,让主进程继续处理命令,所以是 IO最大化。使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 redis的高性能)
(4)相对于数据集大时,比 AOF 的启动效率更高。
缺点:
数据安全性低。RDB 是间隔一段时间进行持久化,如果持久化之间 redis 发生故障,会发生数据丢失。所以这种方式更适合数据要求不严谨的时候。
2、AOF(Append-only file):记录命令行操作,将其保存为 aof 文件。
优点:
(1)数据安全,aof 持久化可以配置 appendfsync 属性,有 always,每进行一次命令操作就记录到 aof 文件中一次。
(2)通过 append 模式写文件,即使中途服务器宕机,可以通过 redis-check-aof工具解决数据一致性问题。
(3)AOF 机制的 rewrite 模式。AOF 文件没被 rewrite 之前(文件过大时会对命令进行合并重写),可以删除其中的某些命令(比如误操作的 flushall))
缺点:
(1)AOF 文件比 RDB 文件大,且恢复速度慢。
(2)数据集大的时候,比 rdb 启动效率低。
5、redis 过期键的删除策略?
(1)定时删除:在设置键的过期时间的同时,创建一个定时器 timer). 让定时器在键的过期时间来临时,立即执行对键的删除操作。
(2)惰性删除:放任键过期不管,但是每次从键空间中获取键时,都检查取得的键是否过期,如果过期的话,就删除该键;如果没有过期,就返回该键。
(3)定期删除:每隔一段时间程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。
6、Redis 的回收策略(淘汰策略)?
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据
注意这里的 6 种机制,volatile 和 allkeys 规定了是对已设置过期时间的数据集淘汰数据还是从全部数据集淘汰数据,后面的 lru、ttl 以及 random 是三种不同的淘汰策略,再加上一种 no-enviction 永不回收的策略。
使用策略规则:
(1)如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用 allkeys-lru
(2)如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random
mongodb
文档数据库,用C++语言编写的非关系型数据库。文件存储格式为BSON(二进制数据流)。
1、常用操作
mongod --help # 查看帮助信息
show dbs
use <db_name>
show collections
db.getCollectionNames().length
db.<collection_name>.find({"key" : "value"}).pretty()
db.<collection_name>.deleteMany({"key" : "value"}) # {}为空时,删除所有数据。{"kye":"value"}删除特定数据
db.<collection_name>.drop() # 删除整个集合包括所有数据和索引
db.collection.insertOne() #单个文档插入到集合中
db.collection.insertMany()# 多个文档插入到集合中
db.collection.insert() # 单个或者多个文件插入到集合中
db.collection.updateOne() # 更新单条
db.collection.updateMany() # 更新多条
db.collection.deleteOne() # 删除单条文档
db.collection.deleteMany() # 删除多条文档
2、聚合查询
Aggregation类似MySQL中的count、sum、group by。聚合内置了很多函数,使用好了这些函数我们就可以统计出我们想要的数据
- $project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。
-
match:{json}使用MongoDB的标准查询操作。
- $limit:用来限制MongoDB聚合管道返回的文档数。
- $skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。
- $group:将集合中的文档分组,可用于统计结果。
- $sort:将输入文档排序后输出。
- $geoNear:输出接近某一地理位置的有序文档。
- $unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。
- aggregation实例
# 过滤status为A, 统计amount字段的总和(这个总和按cost_id分组)
db.orders.aggregate([
{$match:{status:"A"}},
{$group:{_id:"$cost_id", total:{$sum:"$amount"}}}
])
# collection的alias字段是否存在,如果存在,就取alias,否则取host_name字段
db.getCollection('A_collection').aggregate({
$addFields:{
"name":{
$cond:[{$ne:["$alias", undefined]},"$alias","$host_name"]
}
},
{
$project:{
"name":1
}
}
})
- aggregation实例
{ "_id" : 1, "item" : "abc1", qty: 300 }
{ "_id" : 2, "item" : "abc2", qty: 200 }
{ "_id" : 3, "item" : "xyz1", qty: 250 }
# 根据qty的值来生成新的数据(值)
db.inventory.aggregate([
{$project:
{ item: 1,
discount: {$cond: { if: { $gte: [ "$qty", 250 ] }, then: 30, else: 20 }}
}
}])
#output discount是新的键,它根据cond的if判断后,分别被赋上了相应的值
{ "_id" : 1, "item" : "abc1", "discount" : 30 }
{ "_id" : 2, "item" : "abc2", "discount" : 20 }
{ "_id" : 3, "item" : "xyz1", "discount" : 30 }
druid
Druid的核心架构结合了数据仓库(data warehouses),时序数据库(timeseries databases),log搜索系统(logsearch system)三种系统的特点。
特点:1、列式存储。2、可扩展的分布式系统。 3、大规模并行处理。 4、实时或者批数据摄取。 5、自修复、自平衡、易于运维。6、.云原生(Cloud-native)和容错设计不会导致任何数据丢失。7、索引可以实现快速过滤。8、基于时间的分片(time-based partiotioning)。9、近似算法。10、在数据摄入时自动化合并。
基本操作
docker run --rm -i -p 8888:8888 fokkodriesprong/docker-druid # 启动druid
docker exec -it <container_id> bash # 进入容器
curl -X POST -H "Content-Type: application/json" http://localhost:8888/druid/v2/sql --data-binary '{"query":"select * from wikipedia"}' # 查询数据
http://127.0.0.1:8888 # 进入druid console
SQLite
1、常用操作
sqlite3 -version # 查看版本
sqlite3 your-database-file.db # 打开 SQLite 数据库文件
.tables # 查看数据库表
SELECT * FROM table_name; # 查看数据库表
.schema # 查看数据库表结构
.exit # 退出
ETCD
一个用于配置共享和服务发现的KV存储系统。
特点:- 1 键值对存储 - 2 服务注册与发现 - 3 消息发布与订阅 - 4 分布式通知与协调 - 5 分布式锁 - 6 集群监控和Leader选举
etcdctl version # 查看版本
etcdctl --help # 帮助信息
etcdctl put /testdir/testkey "Hello world" # 存储数据
etcdctl get /testdir/testkey # 查看数据
etcdctl del /testdir/testkey # 删除数据 成功返回1 失败返回0
etcdctl watch /testdir/testkey --rev=1 # 监控数据变化,从修订版本号 1 开始监控事件(创建、更新、删除)
etcdctl update /testdir/testkey "Hello world" --prev-value "你好" # 更新数据:只有当键 /testdir/testkey 的当前值为 "Hello world" 时。--prev-value:指定键的当前预期值(类似乐观锁机制)
etcdctl member list # 查询etcd集群实例
etcdctl member remove # 删除 etcd 实例到 etcd 集群中。
etcdctl member add # 添加etcd 实例到 etcd 集群中。
InfluxDB
1、常用操作
安装: rpm -ivh xxx.rpm
server启动: /usr/bin/influxd
配置文件的路径: /etc/influxdb/influxdb.conf
注意:增加数据采用insert的方式,要注意的是 InfluxDB的insert中,表名与数据之间用逗号(,)分隔,tag和field之间用 空格分隔,多个tag或者多个field之间用逗号(,)分隔。
- 基本sql语法:
create databases <db_name>;
show databases;
use <db_name>;
show measurements;
insert <measurement_name>,host=127.0.0.1,monitor_name=test count=1,cpu=0.1 1435362189575692182 //插入数据
//host=127.0.0.1,monitor_name=test --> tag
//count=1,cpu=0.1 --> field 创建表和增加数据是一样的
//1435362189575692182 --> timestamp
precision rfc3339 -- 之后再查询,时间就是rfc3339标准格式【这个语句后面不加;】
select * from <measurement_name> order by time desc limit 10; //查询升序的10条数据
drop measurement <measurement_name>
show tag keys; // 查看一个measurement中所有的tag key
show field keys; // 查看一个measurement中所有的field key
show retention policies; // 查看一个measurement中所有的保存策略(可以有多个,一个标识为default)
# 支持丰富的聚合函数
2、保存策略与连续查询
数据库保存策略的CURD:提供数据保存策略指定数据保留时间,超过指定时间自动删除.
show retention policies on <db_name> //查看当前数据库Retention Policies
create retention policy <rp_name> on <db_name> duration 3w replication 1 default
//rp_name:策略名;
//3w:保存3周,3周之前的数据将被删除,influxdb具有各种事件参数,比如:h,d,w
//replication 1:副本个数,一般为1就可以了;
//default:设置为默认策略
alter retention policy <rp_name> on <db_name> duration 30d default
drop retention policy <rp_name>
//连续查询Continuous Queries:(数据统计采样)
show continuous queries;
create continuous querie <cq_name> on <db_name> begin select sum(count) into <new_ms_name> from <ms_name> group by time(30m) end
//间隔30分钟计算一次count的和,保存至新的ms. 其中:sum(count):计算总和,30m:时间间隔为30分钟;
drop cotinuous query <cq_name> on <db_name>
3、curl交互
curl -G 'http://localhost:8086/query?pretty=true' --data-urlencode "db=testDb" --data-urlencode "q=select + from test order by time desc"
curl -i -XPOST 'http://localhost:8086/write?db=testDb' --data-binary 'test,host=127.0.0.1,monitor_name=test count=1'