一、NoSQL 数据库
主要内容
- Redis开发与实战(内存型)
- MongoDb入门(存储型)
主要应用场景
专门应对高并发,需要高速读写的场景,而就Redis技术而言,它的性能十分优越,可以支持每秒十几万次的读/写操作,其性能远超数据库,并且还支持集群、分布式、主从同步等配置。
- 天猫双11
- 抢红包,抢手机,抢火车票, 抢门票
- ssr 服务端渲染
1.1 Redis
1.1 Redis 特点
- 存储结构特别-字典
- 内存存储与持久化- 缓存
- 功能丰富
- 简单稳定- 简单可依赖
存储结构
Redis是 REmote DIctionary Server (远程字典服务器)的缩写,它以字典结构存储数据。
字典就是js中的object
在js中key只能是字符串
同大多数语言中的字典一样,Redis字典的键值除了可以是字符串,也可以是其他数据类型。
- 字符串
- 散列
- 列表
- 集合
- 有序集合
功能丰富
- redis 可以为每个key设置生存时间,到期会自动删除,这一功能配合出色的性能能让它作为缓存系统来使用
- redis 还可以限定数据占的最大空间,超过后自动删除不需要的key
- redis 的列表类型键还可以用来实现队列,并且支持阻塞式读取,可以很容易实现一个高性能的优先级队列
- redis还支持"发布/订阅",可以基于此构建聊天室等系统
简单稳定
在关系数据库中要获取posts表内id为1的记录的title字段的值可以使用如下SQL语句实现:
SELECT title FROM posts WHERE id=1 LIMIT 1
redis这么读
HGET post:1 title
1.1.2 Redis 安装
- Mac OS
brew install redis
启动
redis-server
修改端口
redis-server --port 6389
- Windows
下载地址:https://github.com/tporadowski/redis/releases
打开文件夹,内容如下:
打开一个 cmd 窗口 使用 cd 命令切换到该目录运行:
redis-server.exe redis.windows.conf
如果想方便的话,可以把 redis 的路径加到系统的环境变量里,这样就省得再输路径了,后面的那个 redis.windows.conf 可以省略,如果省略,会启用默认的。输入之后,会显示如下界面:
这时候另启一个 cmd 窗口,原来的不要关闭,不然就无法访问服务端了
配置文件 redis.windows.conf
# Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程
# 启用守护进程后,Redis会把pid写到一个pidfile中,在/var/run/redis.pid
daemonize no
# 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定
pidfile /var/run/redis.pid
# 指定Redis监听端口,默认端口为6379
# 如果指定0端口,表示Redis不监听TCP连接
port 6379
# 绑定的主机地址
# 你可以绑定单一接口,如果没有绑定,所有接口都会监听到来的连接
bind 127.0.0.1
# 也就是本机
# 当客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
timeout 0
# 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
# debug (很多信息, 对开发/测试比较有用)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
loglevel verbose
# 日志记录方式,默认为标准输出,如果配置为redis为守护进程方式运行,而这里又配置为标准输出,则日志将会发送给/dev/null
logfile stdout
# 设置数据库的数量,默认数据库为0,可以使用select <dbid>命令在连接上指定数据库id
# dbid是从0到‘databases’-1的数目
databases 16
################################ SNAPSHOTTING #################################
# 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
# Save the DB on disk:
#
# save <seconds> <changes>
#
# Will save the DB if both the given number of seconds and the given
# number of write operations against the DB occurred.
#
# 满足以下条件将会同步数据:
# 900秒(15分钟)内有1个更改
# 300秒(5分钟)内有10个更改
# 60秒内有10000个更改
# Note: 可以把所有“save”行注释掉,这样就取消同步操作了
save 900 1
save 300 10
save 60 10000
# 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
rdbcompression yes
# 指定本地数据库文件名,默认值为dump.rdb
dbfilename dump.rdb
# 工作目录.
# 指定本地数据库存放目录,文件名由上一个dbfilename配置项指定
#
# Also the Append Only File will be created inside this directory.
#
# 注意,这里只能指定一个目录,不能指定文件名
dir ./
1.1.3 redis-cli 简易使用
# 启动连接
redis-cli
# 关闭连接
SHUTDOWN
# 自定义地址和端口号(默认服务器地址为127.0.0.1,端口号为6379)
redis-cli -h 127.0.0.1 -p 6379
# ping
ping baidu.com
整数回复
Incr 命令将 key 中储存的数字值增1。如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。如果值包含错误的类型,或字符串类型的值不能表示为数字,那么返回一个错误。
incr abc
set abc 20
incr abc
字符串回复
get foo
(nil)
获取所有变量
keys *
判断一个键是否存在
如果键存在返回整数类型1,否则返回0
exists key
删除键
如果删除成功返回整数类型1,否则返回0
del key [key …]
获得键值的数据类型
set foo 1
# string
type foo
lpush bar 1
# list
type bar
修改配置
# 修改 redis.windows.conf 得 loglevel 配置为 warning
config set loglevel warning
多数据库
可以将其中的每个字典都理解成一个独立的数据库。每个数据库对外都是以一个从0开始的递增数字命名,Redis默认支持16个数据库,可以通过配置参数databases来修改这一数字。客户端与Redis建立连接后会自动选择0号数据库,不过可以随时使用SELECT命令更换数据库,如要选择1号数据库:
redis>SELECT 1
OK
redis [1]>GET foo
(nil)
这些以数字命名的数据库又与我们理解的数据库有所区别
- 不支持自定义数据库的名字,每个数据库都以编号命名,开发者必须自己记录哪些数据库存储了哪些数据
- 不支持为每个数据库己记录哪些数据库存储了哪些数据
- 不支持为每个数据库设置不同的访问密码,所以一个客户端要么可以访问全部数据库,要么连一个数据库也没有权限访问
- 多个数据库之间并不是完全隔离的,比如 FLUSHALL 命令可以清空一个Redis实例中所有数据库中的数据
- 可以使用0号数据库存储某个应用生产环境中的数据,使用1号数据库存储测试环境中的数据,但不适宜使用0号数据库存储A应用的数据而使用1号数据库存储B应用的数据,不同的应用应该使用不同的Redis实例存储数据
- 一个空Redis实例占用的内存只有1MB左右,所以不用担心多个Redis实例会额外占用很多内存
1.1.4 Redis 数据类型
字符串类型
存储的数据的最大容量是512MB
-
赋值与取值
set key value get key
扩展命令
# 增加指定的整数
incrby key increment
# 减少指定的整数
decrby key decrement
# 增加指定浮点数
incrfloat key float
# 向尾部追加值
append key value
# 获取字符串长度
strlen key
# 获取/设置多个键值
mset key1 v1 key2 v2
mget key1 key2
散列类型
散列适合存储对象:使用对象类别和ID构成键名,使用字段表示对象的属性,而字段值则存储属性值
ex: 汽车数据模型
对于2,3这两天数据来说data是多余的,这样会变得越来越难维护。为了解决这个问题,必须新建额外的表进行关联
而redis的散列类型不存在这个问题,我们可以为任何键新增或删除字段而不影响其它。
命令
-
赋值与取值
# 赋值与取值 hset key field value hget key field
当执行的是插入操作时(即之前字段不存在)HSET命令会返回1,当执行的是更新操作时(即之前字段已经存在)HSET命令会返回0。更进一步,当键本身不存在时,HSET命令还会自动建立它
-
获取所有字段和值
hgetall key
不要担心结构不好用,在nodejs中会把返回值封装成js的object
redis.hgetall("car", function (error, car) { // hgetall 方法的返回的值被封装成了 JavaScript的对象 console.log(car.price); }
-
判断字段是否存在
# 存在返回 1,否则返回 0 hexists key field
-
增加数字
hincrby key field increment
-
删除字段
hdel key field [field …]
-
获取字段名和值
hkeys key hvals key
列表类型
列表类型可以存储一个有序的字符串列表,常用的操作是向列表两端添加元素,或者获得列表的某一个片段。
列表类型内部是使用双向链表(double linked list)实现的,所以向列表两端添加元素的时间复杂度为O(1),获取越接近两端的元素速度就越快。这意味着即使是一个有几千万个元素的列表,获取头部或尾部的10条记录也是极快的(和从只有20个元素的列表中获取头部或尾部的10条记录的速度是一样的)。不过使用链表的代价是通过索引访问元素比较慢。
和js的数组有什么区别?
- 在js里面数组其实也是对象,字典
- redis的列表却是链表
命令
-
向列表两端增加元素
# 左边添加 lpush key value [value …] # 右边添加 rpush key value [value …]
-
从列表两端弹出元素
# 左边弹出 lpop key # 右边添加 rpop key
-
获取列表中元素的个数
llen key
-
获取列表片段
lrange key start stop
lrange 0 -1 可以获取列表中所有的元素
-
删除列表中指定的值
lrange key count value
LREM命令会删除列表中前count个值为value的元素,返回值是实际删除的元素个数。根据count值的不同,LREM命令的执行方式会略有差异。
(1)当 count > 0时 LREM 命令会从列表左边开始删除前 count 个值为 value的元素。
(2)当 count < 0时 LREM 命令会从列表右边开始删除前|count|个值为 value 的元素。
(3)当 count = 0是 LREM命令会删除所有值为 value的元素。例如:
命令补充
-
获得/设置指定索引的元素值
LINDEX key index
LSET key index value
-
只保留列表指定片段
LTRIM
命令可以删除指定索引范围之外的所有元素,其指定列表范围的方法和LRANGE命令相同。就像这样:redis> LRANGE numbers 0 -1 1) "1" 2) "2" 3) "7" 4) "3" redis> LTRIM numbers 1 2 OK redis> LRANGE numbers 0 1 1) "2" 2) "7"
LTRIM命令常和LPUSH命令一起使用来限制列表中元素的数量,比如记录日志时我们希望只保留最近的100条日志,则每次加入新元素时调用一次LTRIM命令即可:
LPUSH logs $newLog LTRIM logs 0 99
-
向列表中插入元素
LINSERT key BEFORE|AFTER pivot value
LINSERT 命令首先会在列表中从左到右查找值为 pivot 的元素,然后根据第二个参数是BEFORE还是AFTER来决定将value插入到该元素的前面还是后面。
LINSERT命令的返回值是插入后列表的元素个数。示例如下:
redis> LRANGE numbers 0 -1 1) "2" 2) "7" 3) "0" redis> LINSERT numbers AFTER 7 3 (integer) 4 redis> LRANGE numbers 0 -1 1) "2" 2) "7" 3) "3" 4) "0" redis> LINSERT numbers BEFORE 2 1 (integer) 5 redis> LRANGE numbers 0 -1 1) "1" 2) "2" 3) "7" 4) "3" 5) "0"
-
将元素从一个列表转到另一个列表
RPOPLPUSH source destination
RPOPLPUSH是个很有意思的命令,从名字就可以看出它的功能:先执行RPOP命令再执行LPUSH命令。RPOPLPUSH命令会先从source列表类型键的右边弹出一个元素,然后将其加入到destination列表类型键的左边,并返回这个元素的值,整个过程是原子的。
当把列表类型作为队列使用时,RPOPLPUSH 命令可以很直观地在多个队列中传递数据。当source和destination相同时,RPOPLPUSH命令会不断地将队尾的元素移到队首,借助这个特性我们可以实现一个网站监控系统:使用一个队列存储需要监控的网址,然后监控程序不断地使用 RPOPLPUSH 命令循环取出一个网址来测试可用性。这里使用RPOPLPUSH命令的好处在于在程序执行过程中仍然可以不断地向网址列表中加入新网址,而且整个系统容易扩展,允许多个客户端同时处理队列。
MongoDB
Node 操作JSON 数据方便,MongoDB 采用 JSON 方式存储,查询速度快。
集合类型
集合中的每个元素都是不同的,且没有顺序。一个集合类型(set)键可以存储至多2的32次方 −1个(相信这个数字对大家来说已经很熟悉了)字符串
命令
-
增加/删除元素
SADD key member [member …]
SREM key member [member …]
SADD 命令用来向集合中增加一个或多个元素,如果键不存在则会自动创建。因为在一个集合中不能有相同的元素,所以如果要加入的元素已经存在于集合中就会忽略这个元素。本命令的返回值是成功加入的元素数量(忽略的元素不计算在内)。例如:
redis> SADD letters a (integer) 1 redis> SADD letters a b c (integer) 2
第二条SADD命令的返回值为2是因为元素“a”已经存在,所以实际上只加入了两个元素。
SREM命令用来从集合中删除一个或多个元素,并返回删除成功的个数,例如:
redis> SREM letters c d (integer) 1
由于元素“d”在集合中不存在,所以只删除了一个元素,返回值为1。
2.获得集合中的所有元素
SMEMBERS key
SMEMBERS命令会返回集合中的所有元素,例如:
redis> SMEMBERS letters
1) "b"
2) "a"
-
判断元素是否在集合中
SISMEMBER key member
判断一个元素是否在集合中是一个时间复杂度为O(1)的操作,无论集合中有多少个元素,SISMEMBER命令始终可以极快地返回结果。当值存在时判断一个元素是否在集合中是一个时间复杂度为O(1)的操作,无论集合中有多少个元素,SISMEMBER命令始终可以极快地返回结果。当值存在时 SISMEMBER命令返回1,当值不存在或键不存在时返回0,例如:
redis> SISMEMBER letters a (integer) 1 redis> SISMEMBER letters d (integer) 0
4.集合间运算
SDIFF key [key „]
SINTER key [key „]
SUNION key [key „]
接下来要介绍的3个命令都是用来进行多个集合间运算的。
1)SDIFF命令用来对多个集合执行差集运算。集合A与集合B的差集表示为A−B,代表所有属于A且不属于B的元素构成的集合(如图3-13所示),即A−B ={x | x∈A且x∈B}。例如:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ljnTFcf6-1660099484299)(./assets/image-20190611140833117.png)]
{1, 2, 3} - {2, 3, 4} = {1} {2, 3, 4} - {1, 2, 3} = {4} SDIFF命令的使用方法如下:
redis> SADD setA 1 2 3
(integer) 3
redis> SADD setB 2 3 4
(integer) 3
redis> SDIFF setA setB
1) "1"
redis> SDIFF setB setA
1) "4"
SDIFF命令支持同时传入多个键,例如:
redis> SADD setC 2 3
(integer) 2
redis> SDIFF setA setB setC
1) "1"
(2)SINTER命令用来对多个集合执行交集运算。集合A与集合B的交集表示为A ∩ B,代表所有属于A 且属于B的元素构成的集合(如图3-14所示),即A ∩ B ={x | x ∈ A 且x ∈B}。
例如: {1, 2, 3} ∩ {2, 3, 4} = {2, 3} SINTER命令的使用方法如下:
redis> SINTER setA setB
1) "2"
2) "3"
SINTER命令同样支持同时传入多个键,如:
redis> SINTER setA setB setC
1) "2"
2) "3"
(3)SUNION命令用来对多个集合执行并集运算。集合A与集合B的并集表示为A∪B,代表所有属于A 或属于B的元素构成的集合(如图3-15所示)即A∪B ={x | x∈A或x ∈B}。
例如: {1, 2, 3} ∪{2, 3, 4} = {1, 2, 3, 4}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zcmHzPks-1660099484301)(./assets/image-20190611141307449.png)]
redis> SUNION setA setB
1) "1"
2) "2"
3) "3"
4) "4"
SUNION命令同样支持同时传入多个键,例如:
redis> SUNION setA setB setC
1) "1"
2) "2"
3) "3"
4) "4"
命令补充
-
获取集合中的元素个数
SCARD key
redis> SMEMBERS letters 1) "b" 2) "a" redis> SCARD letters (integer) 2
-
进行集合运算并将结果存储
SDIFFSTORE destination key [key …]
SINTERSTORE destination key [key …]
SUNIONSTORE destination key [key …]
-
随机获得集合中的元素
SRANDMEMBER key [count]
还可以传递count参数来一次随机获得多个元素,根据count的正负不同,具体表现也不同。
(1)当count为正数时,SRANDMEMBER会随机从集合里获得count个不重复的元素。如果count的值大于集合中的元素个数,则SRANDMEMBER会返回集合中的全部元素。
(2)当count为负数时,SRANDMEMBER会随机从集合里获得|count|个的元素,这些元素有可能同。 -
从集合中弹出一个元素
SPOP key
有序集合类型
在集合类型的基础上有序集合类型为集合中的每个元素都关联了一个分数,这使得我们不仅可以完成插入、删除和判断元素是否存在等集合类型支持的操作,还能够获得分数最高(或最低)的前N个元素、获得指定分数范围内的元素等与分数有关的操作。虽然集合中每个元素都是不同的,但是它们的分数却可以相同。 有序集合类型在某些方面和列表类型有些相似。
(1)二者都是有序的。
(2)二者都可以获得某一范围的元素。
但是二者有着很大的区别,这使得它们的应用场景也是不同的。
(1)列表类型是通过链表实现的,获取靠近两端的数据速度极快,而当元素增多后,访问中间数据的速度会较慢,所以它更加适合实现如“新鲜事”或“日志”这样很少访问中间元素的应用。
(2)有序集合类型是使用散列表和跳跃表(Skip list)实现的,所以即使读取位于中间部分的数据速度也很快(时间复杂度是O(log(N)))。
(3)列表中不能简单地调整某个元素的位置,但是有序集合可以(通过更改这个元素的分数)。
(4)有序集合要比列表类型更耗费内存。
命令
-
增加元素
ZADD key score member [score member...]
ZADD 命令用来向有序集合中加入一个元素和该元素的分数,如果该元素已经存在则会用新的分数替换原有的分数。ZADD命令的返回值是新加入到集合中的元素个数(不包含之前已经存在的元素)。
# 记录Tom、Peter和David三名运动员的分数(分别是89分、67分和100分) redis> ZADD scoreboard 89 Tom 67 Peter 100 David (integer) 3 # 修改Peter的分数 redis> ZADD scoreboard 76 Peter (integer) 0
-
获得元素的分数
ZSCORE key member
redis> ZSCORE scoreboard Tom "89"
-
获得排名在某个范围的元素列表
ZRANGE key start stop [WITHSCORES]
ZREVRANGE key start stop [WITHSCORES]
ZRANGE命令会按照元素分数从小到大的顺序返回索引从 start到stop之间的所有元素(包含两端的元素)。ZRANGE命令与LRANGE命令十分相似,如索引都是从0开始,负数代表从后向前查找(−1表示最后一个元素)。就像这样:
redis> ZRANGE scoreboard 0 2 1) "Peter" 2) "Tom" 3) "David" redis> ZRANGE scoreboard 1 -1 1) "Tom" 2) "David"
-
获得指定分数范围的元素
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
# 返回分数在min和max之间(包含min和max)的元素 redis> ZRANGEBYSCORE scoreboard 80 100 1) "Tom" 2) "David"
如果希望分数范围不包含端点值,可以在分数前加上“(”符号。例如,希望返回”80分到100分的数据,可以含80分,但不包含100分,则稍微修改一下上面的命令即可:
redis> ZRANGEBYSCORE scoreboard 80 (100 1) "Tom"
-inf和+inf分别表示负无穷和正无穷。得到所有分数高于80分(不包含80分)的人
redis> ZRANGEBYSCORE scoreboard (80 +inf 1) "Tom" 2) "David"
-
增加某个元素的分数
ZINCRBY key increment member
# 给 Jerry加4分 redis> ZINCRBY scoreboard 4 Jerry "60" redis> ZINCRBY scoreboard -4 Jerry "56"
命令补充
-
获取集合中元素的数量
ZCARD key
redis> ZCARD scoreboard (integer) 6
-
获得指定分数范围内的元素个数
ZCOUNT key min max
redis> ZCOUNT scoreboard 90 100 (integer) 2 redis> ZCOUNT scoreboard (89 +inf (integer) 2
-
删除一个或多个元素
ZREM key member [member …]
redis> ZREM scoreboard Wendy (integer) 1 redis> ZCARD scoreboard (integer) 5
-
按照排名范围删除元素
ZREMRANGEBYRANK key start stop
ZREMRANGEBYRANK 命令按照元素分数从小到大的顺序(即索引 0表示最小的值)删除处在指定排名范围内的所有元素,并返回删除的元素数量。如:
redis> ZADD testRem 1 a 2 b 3 c 4 d 5 e 6 f (integer) 6 redis> ZREMRANGEBYRANK testRem 0 2 (integer) 3 redis> ZRANGE testRem 0 -1 1) "d" 2) "e" 3) "f"
-
按照分数范围删除元素
ZREMRANGEBYSCORE key min max
ZREMRANGEBYSCORE命令会删除指定分数范围内的所有元素,参数min和max的特性和ZRANGEBYSCORE命令中的一样。返回值是删除的元素数量。如:
redis> ZREMRANGEBYSCORE testRem (4 5 (integer) 1 redis> ZRANGE testRem 0 -1 1) "d" 2) "f"
-
获得元素的排名
ZRANK key member
ZREVRANK key member
ZRANK命令会按照元素分数从小到大的顺序获得指定的元素的排名(从0开始,即分数最小的元素排名为0)。如:
redis> ZRANK scoreboard Peter (integer) 0
ZREVRANK命令则相反(分数最大的元素排名为0):
redis> ZREVRANK scoreboard Peter (integer) 4
1.2 MongoDB
1.2.1 下载与安装
运行 MongoDB 服务器,必须从 MongoDB 目录的 bin 目录中执行 mongod.exe 文件
- mongod.exe 是服务器,需先启动
- mongo.exe 是客户端,等服务器启动就可以连接
# 创建数据库、表都在 D:\MongoDB\data\db 这个目录下,必须以管理员身份运行
D:\mongodb\bin\.\mongod.exe --dbpath=D:\MongoDB\data\db
1.2.2 命令行操作
查看命令
# 帮助
help
db.help()
db.test.help()
db.test.find().help()
# 查看所有数据库名称
show dbs
# 查看当前使用的数据库
db
db.getName()
# 查看当前 db 状态
db.stats()
# 查看当前 db 的所有集合
db.getCollectionNames()
操作数据库命令
# 创建/切换数据库
use [name]
# 创建集合,至少有一个集合才能被 show dbs 查看
db.createCollection([name])
# 删除当前数据库
db.dropDatabase()
# 删除集合
db.[name].drop()
操作集合命令
假设操作 users 集合
# 插入数据
db.users.save([data])
# 查询所有数据
db.users.find()
# 查询第一条数据
db.users.findOne()
# 查询 age: 18 的数据
db.users.find({ age: 18 })
# 查询 age 大于 18 的数据,小于是 $lt
db.users.find({ age: {$gt: 18} })
# 查询 age 大于等于 18 的数据
db.users.find({ age: {$gte: 18} })
# 查询 age 大于等于 18, 小于等于 100 的数据
db.users.find({ age: {$gte: 18, $lte: 100} })
# 查询出 age 字段的数据(包含主键 _id,其他数据不显示)
db.users.find({}, { age: 1 })
# 查询出 age 字段的数据(不包含主键 _id,其他数据不显示)
db.users.find({}, { age: 1, _id: 0 })
# 升序排列,倒序排列
db.users.find().sort({ age: 1 })
db.users.find().sort({ age: -1 })
# 分页查询, skip 是跳过多少数据,limit 是取多少数据
db.users.find().skip([page] * [pageSize]).limit([pageSize])
# 查询 name 包含 k 的数据,传正则
db.users.find({ name: /k/ })
# 查询 age: 18 或 age: 100 的数据
db.users.find({ $or: [{age: 18}, {age: 100}] })
# 查询数据条数
db.users.find().count()
# 删除数据,删除所有 age: 100 的数据
db.users.remove({ age: 100 })
# 删除所有数据
db.users.remove({})
# 更新数据,{ age: 18 } 替换 { name: 'zs' } 的数据
db.users.update({ name: 'zs' }, { age: 18 })
# 更新数据,{ name: 'zs' } 的数据修改
db.users.update({ name: 'zs' }, {$set: { age: 18 }})
# 更新数据,{ name: 'zs' } 的数据修改,age 字段 +10
db.users.update({ name: 'zs' }, {$inc: { age: 10 }})
1.2.3 node 连接 MongoDB
npm i mongoose
1)连接数据库
// 1. db.config.js
const mongoose = require('mongoose')
// 127.0.0.1:27017 是数据库地址,express_project 是数据库名称(插入数据时会自动创建)
mongoose.connect('mongodb://127.0.0.1:27017/express_project')
2)入口文件引入 db.config.js
// 2. app.js
require('./db.config')
3)创建模型(集合)
// 3. model/UsersModel.js
const mongoose = require('mongoose')
// 限制集合类型
const Schema = mongoose.Schema
const UserType = {
name: String,
password: String,
age: Number
}
// 创建模型 user, 数据库将会对应 users
const UserModel = mongoose.model('user', new Schema(UserType))
module.exports = UserModel
4)操作数据库
const UserModel = require('../model/UserModel')
// 4. 假设发送了一个 post 请求创建数据
router.post('/add', function(req, res) {
const { name, password, age } = req.body
// 增 删 改 查/UserModel.create UserModel.delete UserModel.update UserModel.find
UserModel.create({
name,
password,
age
}).then(data => {
console.log(data)
})
})
1.2.4 node 操作 MongoDB
1)增
const UserModel = require('../model/UserModel')
// 创建一条数据
const name = '张三'
const password = 123
UserModel.create({
name,
password
}).then(data => {
// data 是新创建好的数据
console.log(data)
})
2)删
const UserModel = require('../model/UserModel')
// 删除 _id 匹配上的一条数据
UserModel.deleteOne({ _id: '62b2af3f39a05443d53b02b8' }).then(data => {
console.log(data)
})
3)改
const UserModel = require('../model/UserModel')
// 更新 _id 匹配上的一条数据
UserModel.updateOne({ _id: '62b2af3f39a05443d53b02b8' },{
name: '李四',
password: 123456
}).then(data => {
console.log(data)
})
4)查
const UserModel = require('../model/UserModel')
// 查询所有数据
UserModel.find().then(data => {
console.log(data)
})
二、MySQL
2.1 基本命令
进入命令行环境: mysql -uroot -p
查看有哪些数据库: show databases;
使用某个数据库: use 数据库名
查看数据库内有哪些表: show tables;
退出MySQL环境: exit
2.2 图形化工具
2.3 数据类型
2.3.1 整数类型
类型 | 字节 | 范围 | 描述 |
---|---|---|---|
TINYINT | 1 | (-128, 127) | 小整数值 |
SMALLINT | 2 | (-32 768, 32 767) | 大整数值 |
MEDIUMINT | 3 | (-8 388 608, 8 388 607) | 大整数值 |
INT 或 INTEGER | 4 | (-2 147 483 648, 2 147 483 647) | 大整数值 |
BIGINT | 8 | - | 极大整数值 |
FLOAT | 4 | - | 单精度浮点值 |
DOUBLE | 8 | - | 双精度浮点值 |
2.3.2 字符串类型
类型 | 字节 | 描述 |
---|---|---|
CHAR | 0-255 | 定长字符串 |
VARCHAR | 0-255 | 变长字符串 |
TINYBLOB | 0-255 | 不超过 255 个字符的二进制字符串 |
TINYTEXT | 0-255 | 短文本字符串 |
BLOB | 0-65535 | 二进制形式的长文本数据 |
TEXT | 0-65535 | 长文本数据 |
MEDIUMBLOB | 0-16 777 215 | 二进制形式的中等长度文本数据 |
MEDIUMTEXT | 0-16 777 215 | 中等长度文本数据 |
LONGBLOB | 0-4 294 967 295 | 二进制形式的极大文本数据 |
LONGTEXT | 0-4 294 967 295 | 极大文本数据 |
VARBINARY(M) | - | 允许长度0-M个字节的定长字符串,值的长度 +1 个字节 |
BINARY(M) | - | 允许长度0-M个字节的定长字符串 |
2.3.3 日期类型
类型 | 字节 | 格式 | 描述 |
---|---|---|---|
DATE | 4 | YYYY-MM-DD | 日期值 |
TIME | 3 | HH:MM:SS | 时间值 |
YEAR | 1 | YYYY | 年份值 |
DATETIME | 8 | YYYY-MM-DD HH:MM:SS | 日期和时间值 |
TIMESTAMP | 4 | - | 时间戳 |
2.3.4 复合类型
ENUM 类型
只允许在集合中取一个值,类似单选项。处理互斥的数据时容易理解,比如人类的性别。ENUM 类型可以从集合中取得一个值或使用 null 值
SET 类型
可以从预定义的集合中取得任意数量的值
2.4 SQL 语句
进入命令行环境: mysql -uroot -p
SQL 分类
- 数据定义: DDL(Data Definition Language)
- 库的创建删除、表的创建删除等
- 数据操纵: DML(Data Manipulation Language)
- 新增数据、删除数据、修改数据等
- 数据控制: DCL(Data Control Language)
- 新增用户、删除用户、密码修改、权限管理等
- 数据查询: DQL(Data Query Language)
- 基于需求查询和计算数据
SQL 特征
- SQL 语句,大小写不敏感
- SQL 可以单行或多行书写,最后以;号结束
- SQL支持注释:
- 单行注释:
-- 注释内容
(-- 后面一定要有一个空格) - 单行注释:
# 注释内容
(# 后面可以不加空格,推荐加上) - 多行注释:
/* 注释内容 */
- 单行注释:
2.4.1 基础语句和 DDL
# 查看数据库
show databases;
# 使用数据库
use 数据库名;
# 创建数据库
create database 数据库名 [charset utf8];
# 删除数据库
drop database 数据库名;
# 查看当前使用的数据库
select database();
# 查看表(得先选中库)
show tables;
# 创建表
create table 表名(
列名称 列类型,
列名称 列类型,
......
);
# 删除表
drop table 表名称;
2.4.2 DML
插入语法
insert into 表[(列1, 列2, ..., 列n)] values(值1, 值2, ..., 值n)[,(值1, 值2, ..., 值n), ..., (值1, 值2, ..., 值n)]
# student表的id, name, age列插入两条数据
insert into student(id, name, age) values(1, '张三', 18),(2, '李四', 20);
# student表的所有列插入一条数据,值按列的顺序插入
insert into student values(3, '王五', 25);
删除语法
delete from 表名称 [where 条件判断]
条件判断: 列 操作符 值
# 删除id等于1的数据
delete from student where id = 1;
# 删除整个表
delete from student;
更新语法
update 表名 set 列=值 [where 条件判断]
条件判断: 列 操作符 值
# 把id等于1的数据的age列的值改为18
update student set age = 18 where id = 1;
# 把整个表age列的值改为18
update student set age = 18;
2.4.3 DQL
基础查询
select 字段列表|* from 表名
# 查询id,name列
select id, name from student;
# 查询age = 18的数据
select * from student where age = 18;
分组聚合
select 字段|聚合函数 from 表名 [where 条件] group by 列
聚合函数有:
sum(列)
求和avg(列)
求平均值min(列)
求最小值max(列)
求最大值count(列|*)
求数量
注意:group by 中出现了哪个列,哪个列才能出现在 select 中的非聚合中
# 查询gender列, age列的平均值
select gender, avg(age) from student group by gender;
排序
select 列|聚合函数|* from 表 order by 列 [asc | desc]
asc: 升序 desc: 降序
# 降序查询student表age大于20的数据
select * from student where age > 20 order by age desc;
分页
select 列|聚合函数|* from 表 limit n[,m]
# 查询student表前五条数据
select * from student limit 5;
# 查询student表从第十一条开始取五条数据
select * from student limit 10,5;
2.5 连接 MySQL
npm i mysql2
const express = require('express')
const mysql2 = require('mysql2')
const app = express()
app.get('/', async (req, res) => {
// 创建连接池
const dbConfig = getDBConfig()
const promisePool = mysql2.createPool(dbConfig).primise()
// sql 查询
const users = await promisePool.query('select * from students')
console.log(users[0])
res.send({
code: 0,
data: users[0]
})
})
// 数据库信息
function getDBConfig() {
return {
host: '127.0.0.1', // 地址
port: 3306, // 端口
user: 'root', // 账号
password: '123', // 密码
database: 'tset', // 数据库名
connectionLimit: 1 // 1 个连接池
}
}
app.listen(3000)