数据库学习笔记(1)
2020/04/14 add 数据库查询(mongoDB)
2020/04/17 add 数据库聚合函数
2020/04/17 add 关系型数据库和非关系型数据库
2020/04/19 add 数据库事务
2020/05/08 add mongoDB
2020/05/13 add 数据库删除
2020/05/20 add redis
2020/07/20 add mongoose mongodb schemaless
2020/07/23 add 元数据
2020/09/12 add mongoDB 精准查询
–
2020/09/22 add mongoDB 去重
2020/12/25 add mongoDB 关联查询
2021/01/20 add DB 基本概念
2021/02/22 add BSON 和 JSON 区别
2021/05/08 add LRU 数据库冗余,反向索引
2021/09/13 add 数据库 db-driver
DB 基本概念
连接数据库的三要素
- 数据源 DSN(Data Source Name):
数据库类型:host=数据库的主机地址;dbname=默认的数据库名称等
- 用户名:username
- 用户密码:password
元数据
元数据是用来描述数据的数据(Data that describes other data)。
元数据最大的好处是,它使信息的描述和分类可以实现格式化,从而为机器处理创造了可能。
mongoose
mongoose 是 mongodb 对象建模工具,旨在异步环境中工作。
一些注意点:
彻底搞懂Mongoose中update,updateOne,updateMany和findOneAndUpdate
- mongoose里没有
findAndModify
方法 findOneAndUpdate
,实际调用的是findAndModify
,这个的好处是会返回文档,设置{new: true}
返回更新后的文档,默认为false
.
如果想用原生的findOneAndUpdate
, 就设置mongoose.set('useFindAndModify', false);
或者
mongoose.connect(uri, { useFindAndModify: false });
如果用原生findOneAndUpdate
,可以设置returnNewDocument
属性,返回修改后的值
关于 findOneAndUpdate
中常用的 修改器:MongoDB之
关
键
字
,
以
及
关键字,以及
关键字,以及修饰器
s
e
t
,
set,
set,inc,
p
u
s
h
,
push,
push,pull,$pop
查询符合对象数组中某个对象的值
// 数据结构
[
{
_id:'0',
publisher:{
uid:'123',
}
subscriber:[
{
mobile:'xxxx',
email:'xxa@163.com'
},
{
email:'xxb@163.com'
}
]
},
{
_id:'1',
publisher:{
uid:'456',
}
subscriber:[
{
email:'xxa@163.com'
},
{
email:'xxb@163.com'
}
]
},
{
_id:'2',
publisher:{
uid:'456',
}
subscriber:[
{
email:'xxa@163.com'
},
]
},
{
_id:'3',
publisher:{
uid:'456',
}
subscriber:[
{
phone:'18311109091'
email:'xxa@163.com'
},
{
email:'xxb@163.com'
}
]
},
]
需求: 查出订阅者邮箱是xxa@163.com
的所有通知历史。此时若使用 subscriber:{email:'xxa@163.com'}
进行查询只会查出来_id:2
的数据,此时应该这么查询:
reference:MongoDB操作符之$elemMatch
// shell
// 注意这种查询方式会查出来 subscriber 这个数组中任何有一个元素满足即返回
db.getCollection('notices').find({"subscriber.email":"xxa@163.com","subscriber.phone":"18222308101"})
// 注意 $elemMatch 有更准确用法:The $elemMatch operator matches documents that contain an array field with at least one element that matches all the specified query criteria. 即只会返回完全满足 email 和 phone 条件的元素
db.getCollection('notices').find({subscriber:{$elemMatch:{email:"xxa@163.com",phone:"18222308101"}}})
// 有关 mongoose api 基于以上进行相应改变即可
数据库删除
逻辑删除
逻辑删除记录,不会直接删除数据库中的数据,仅是通过某些手段屏蔽被逻辑删除的数据在前台的显示,不会释放物理空间,并且还可以从数据库中查得数据。
物理删除
物理删除记录,即是会将数据库中的数据记录直接清除(也可以说是磁盘上的删除),会释放出物理空间,也将不能再从数据库中搜索到删去的数据记录;
后记
一般来讲大部分公司都采用逻辑删除的方式:对数据记录用处极大
mongoDB
__v字段
“__v” 是 “versionKey"的简写,当每一个文档由mongoose创建时就会自动添加,代表这该文档的版本,此属性可配置修改,默认为“__v”,
作用是可以在"save文档"时作为一个查询条件,避免在"取出数据"到"save数据"的这段时间内,数据被其他进程修改而导致冲突。
MongoDB 查询
模糊查询
Evaluation Query Operators
使用 $regex
操作符以及 PCRE 即可
精准查询
但有的时候也会用到精准查询:
stackoverflow 查询之后通常用这种 text search 的方式进行查询:
建立索引 ==> 查询
注:需要注意的是可以通过对单个字段建立索引进行查询,因此并不意味着 text
是全文检索
关联查询
shell 可以参考 $lookup
mongoose 可以参考 virtualType 与 populate
schemaless vs schemafree
mongodb schemafree 即:插入额外的字段或者说列,不存在即允许插入,不会禁用。实际上这跟 mongdodb 本身是一个 NOSQL db 有关,which does not use the concept of tables and columns, instead of which it uses the concept of documents and collections. All the referential data with respect to different modules will be stored as one collection. More over the BSON data structure used by MongoDB can easily have varying sets of data and fields with different types.
When we say schemaless, we actually mean dynamically typed schema, as opposed to statically typed schemas as available in RDBMS(SQL) databases. JSON is a completely schema free data structure, as opposed to XML which allows you to specify XSD if you need.
但针对 schema-free 进行限制 save 操作的需求该怎么处理
MongoDB 去重
distinct 去重
注: 但实际上这种去重方式有其局限性就是不能超过 16MB,否则会报类似错 Error: distinct too big, 16mb cap
去重
MongoDB 的 _id
通常,在一个集合中,没写入一行数据都会自动生成一个唯一标识 _id,它为什么这么轻量又能保证全局唯一的呢?
前4 个字节是从标准纪元开始的时间戳,单位为秒。这会带来一些有用的属性。时间戳,与随后的. 5 个字节组合起来,提供了秒级别的唯一性。由于时间戳在前,这意味着ObjectId 大致会按照插入的顺序排列。这对于某些方面很有用,如将其作为索引提高效率,但是这个是没有保证的,仅仅是“大致”。这4 个字节也隐含了文档创建的时间。绝大多数驱动都会公开一个方法从ObjectId 获取这个信息。因为使用的是当前时间,很多用户担心要对服务器进行时间同步。其实没有这个必要,因为时间戳的实际值并不重要,只要其总是不停增加就好了(每秒一次)。
接下来的3 字节是所在主机的唯一标识符。通常是机器主机名的散列值。这样就可以确保不同主机生成不同的ObjectId,不产生冲突。
为了确保在同一台机器上并发的多个进程产生的ObjectId 是唯一的,接下来的两字节来自产生ObjectId 的进程标识符(PID)。
前9 字节保证了同一秒钟不同机器不同进程产生的ObjectId 是唯一的。后3 字节就是一个自动增加的计数器,确保相同进程同一秒产生的ObjectId 也是不一样的。同一秒钟最多允许每个进程拥有2563(16 777 216)个不同的ObjectId。
MongoDB 的 cursor
游标是什么?
通俗的说,游标不是查询结果,而是查询的一个返回资源或者接口,通过
这个接口,可以逐条读取数据。这里尤其适用于在查询大量数据的场景。
toArray 的优化或者说替代的高效方式有哪些?
mongodb 16MB 到底限制了哪些操作?
众所周知,mongodb 有单个文档 16MB 的限制,实际操作中受限于这个限制的操作其实多于写操作,甚至还包含读操作,query 较大操作?
起因是解决用户问题时发现,当用户查询数据量较大时同样会出现如上所示的问题
[ERR_OUT_OF_RANGE]: The value of "offset" is out of range. It must be >= 0 && <= 17825792. Received
- 一种揣测是 MongoDB 一次查询返回数据也是包成一个 BSON,因此同样是受控制的。
其他比如 query 体过大导致报错的现象,可以参考:
Mongodump crash if query file is to large
BSON 和 JSON 的区别
references:
- BSON( Binary Serialized Document Format) 是一种二进制形式的存储格式,其是一种类 json 的一种二进制形式的存储格式,它和 JSON 一样,支持内嵌的文档对象和数组对象,但是 BSON 有 JSON 没有的一些数据类型,如 Date 和 BinData 类型。
- BSON 是 schema less 的,作为 MongoDB 的数据存储格式,因此 MongoDB 数据存储也是 schema less 的。
- BSON 有更快的遍历速度,更容易操作修改,增加了额外的数据类型。
数据库事务
概念
数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。
性质
- 原子性(Atomicity):事务中的全部操作在数据库中是不可分割的,要么全部完成,要么全部不执行。
- 一致性(Consistency):几个并行执行的事务,其执行结果必须与按某一顺序 串行执行的结果相一致。
- 隔离性(Isolation):事务的执行不受其他事务的干扰,事务执行的中间结果对其他事务必须是透明的。
- 持久性(Durability):对于任意已提交事务,系统必须保证该事务对数据库的改变不被丢失,即使数据库出现故障。
事务的ACID特性是由关系数据库系统(DBMS)来实现的,DBMS采用日志来保证事务的原子性、一致性和持久性。日志记录了事务对数据库所作的更新,如果某个事务在执行过程中发生错误,就可以根据日志撤销事务对数据库已做的更新,使得数据库同滚到执行事务前的初始状态。
对于事务的隔离性,DBMS是采用锁机制来实现的。当多个事务同时更新数据库中相同的数据时,只允许持有锁的事务能更新该数据,其他事务必须等待,直到前一个事务释放了锁,其他事务才有机会更新该数据。
关系型数据库 vs 非关系型数据库
工作原因,平常会比较多用mongoDB,而它实际区别于以前用过的譬如mysql oracle等
聚合函数 DB
聚合函数是什么
聚合函数对一组值执行计算并返回单一的值。
在数据库当中,函数分为两种:单行函数和多行函数,相应概念如下:
单行函数:每一行返回一个数值(如lower)
多行函数:多行返回一个数值(如count)
聚合函数:多行函数,即表中的多条记录返回至一个数值,通常用于分组的相关信息。
常见的聚合函数
聚合函数的分类:(常用的5个)
- count:
- count(*):统计表中所有记录的个数
- count(列名):统计一列中值的个数,其中重复的记录也会被当做有效的记录。
- count(distinct 列名):统计一列中值的个数,其中重复的记录只会被记录一次。
- sum(列名):计算一列值的总和。
- avg(列名):计算一列值的平均值。
- max(列名):计算一列值中的最大值。
- min(列名):计算一列值中的最小值。
我认为只要记住count为统计一列中值的个数就可以了,因为里面毕竟是distinct的用法。
注意点
- 聚合函数同order by、distinct、top等都是一样的,都是作用于最终的结果集合的,而不是最用于单行元组的,所以在SQL语句的处理过程当中一定要分清该关键字是作用域单行记录的,还是作用于最终的结果集合的。
- 在聚合函数遇到空值的时候,除count(*)外,所有的聚合函数都会跳过空值而只处理非空值。
- 单行函数和多行函数不能混合使用。
- 如果未对查询结果进行分组,聚集函数将作用于整个查询结果,而分组后聚集函数将作用于每一个组,即每一个组都有一个函数值。
数据库查询mongodb
模糊查询
{name:/xxx/} // 包含
{name:/^xxx/} // 以xxx开头
{name:/xxx^/} // 以xxx结尾
{name:/xxx/i} // 忽略大小写
条件查询
db.collections.find({'name':{$in:['tommy'],['Ammy']}) // 查询名字叫Tommy 和 Ammy的人
查询是否含有某个字段
mongodb中find n e n u l l 与 ne null 与 nenull与exists的区别
$ne:null $exists:true
的区别
数据库 tips
mongoose doc.save 更新问题
且看 mongoose 文档 Since it is a schema-less type, you can change the value to anything else you like, but Mongoose loses the ability to auto detect and save those changes. To tell Mongoose that the value of a Mixed type has changed, you need to call doc.markModified(path), passing the path to the Mixed type you just changed. 因此在对具体 doc 进行更新时可以将其标记为 markModified
mongoose autoCreate
区别于 mongoClient, mongoose 做了一部分优化,那就是可以不在建立 model 的时候自动创建 collection
'autoCreate': Set to true to make Mongoose call Model.createCollection() automatically when you create a model with mongoose.model() or conn.model(). This is useful for testing transactions, change streams, and other features that require the collection to exist.
LRU
其实前文已经在 Redis 中提过 LRU,LRU 是 Redis 的一种逐出策略,当然平常业务中如果不使用 Redis 如何做简单的缓存呢,答案是可以采用 quick-lru 等包实现。
这篇文章概述了简单的 LRU 实现方式: 主要采用双向链表和 hashMap(至于为什么不用单向链表是因为单向链表删除尾节点的时候需要遍历整个链表显而易见效率是低的)
LRU 原理和 Redis 实现
db-driver
What is a Database Driver? Access Database in VS Code. R. Python. Java
db driver 可以看做是一个连接数据库和其他系统之间的一个适配器,它实现了 ODBC 或 JDBC 数据量连接协议。
A database driver is a computer program that implements a protocol (ODBC or JDBC) for a database connection.
connection pool
Connection Pooling with MongoDB
在软件工程中,连接池是维护数据库连接的缓存,可以理解成预先在池子中放进去一些连接,需要时从连接池取出即可,使用完毕再放回去以便将来需要对数据库的请求时可以复用该连接。 连接池用于提高在数据库上执行命令的性能。 在连接池中,创建连接后,将其放入池中并再次使用,这样就不必建立新连接。 如果所有连接都被使用,则会建立一个新连接并将其添加到池中。 连接池还减少了用户必须等待建立到数据库的连接的时间。
node-mongodb-native(node mongodb driver) 支持 connection pool,普通使用中我们一般在应用启动时即建立连接(或单例模式),此后整个应用复用这一个连接进行各种数据库操作。
个人理解: 当高并发的时候,此时会由于连接池中的一个连接在使用而需要新建一个连接再放入到连接池中进行使用。