Redis支持持久化,memcached不支持
memcached所有值都是简单字符串,redis支持更多数据结构
查看启动情况:
ps -ef | grep redis
netstat -antpl | grep redis
Redis-cli -h 192.157.11.8 -p 6379
ping 返回:pong
set hello valueworld 返回:ok
get hello 返回:valueworld
状态回复
ping
返回:pong
错误回复
hget hello filed1
(error)wrongType operation against
整数回复
incr hello
(integer) 1
字符串回复
get hello
world
get hello foo
world
bar
redis.conf文件
./redis-server redis.conf
keys *
MSET key1 "Hello" key2 "World" haha "hehe"
keys ke*
返回:key1,key2
keys ha[g-i]* //第三个字母在g~i之间
返回:haha
keys key? //?代表一个位置长度
返回:key1,key2
dbsize // 计算key的总数
exist keyName
del key [key...]
expire key seconds
ttl key // 查看key还有多长时间过期,若为-1代表key存在且没有过期时间,-2代表已经不存在了
persist key //去掉key的过期时间
type key // 查看key的数据类型,可能是none、string、hash、list、set、zset
<groupId> redis.clients
<artifactId> jedis
<scope> compile
Jedis jedis = new Jedis("127.0.0.1",6379)
jedis.set("hello","world")
jedis.hset("myhash","field1","value1")
jedis.rpush
...
GenericObjectPoolConfig poolConfig = new ...
JedisPool jedisPool = new JedisPool(poolConfig, ip, port)
Jedis jedis = jedisPool.getResource()
jedis.close() //pool中的连接close是归还资源,而不是关闭连接
// 应用个数 * maxtotal 不能超过RedisServer的最大连接数
maxTotal:最大连接数,默认8,建议50个,实际可以偏大
maxIdle:允许最大空闲数,默认8,建议等于maxTotal
minIdle:默认为0,可以预热先让连接池有一些连接
jmxEnabled:开启jmx监控,建议开启,默认为true
blockWhenExhausted:资源池用尽之后,调用者是否等待,默认true
maxWaitMillis:最大等待时间,-1表示永不超时,不建议使用-1
testOnBorrow/return:借还连接时,是否做有效性检测ping/pong,默认false,建议false
1.池子连接都被hang住,例如所有连接都执行keys *
2.QPS高,池子太小
// geoadd key longitude latitude member
geoadd cities:locations 116.7 78.9 beijin
字符串big对应的二进制
01010101 101010100 01010100
set hello big
getbit hello 23 //24位,最后一位是0
setbit key offset value // value只能是0和1,offset是偏移量
userId最好为自增类型的,数值不要超过4294967295 即(2^32)-1
01000101001001 // 0代表今天没登录,1代表登录了
当活跃用户占总用户数很少的时候,例如:1亿用户只有一个活跃用户,这个时候还不如使用set直接保存活跃用户id即可
新加入的订阅者不能收到之前发布的消息,订阅状态,处于此状态下客户端不能使用除subscribe、unsubscribe、psubscribe和punsubscribe这四个属于"发布/订阅"之外的命令,否则会报错
// publish channel message
publish souhu:tv "hello world"
返回:3,订阅者个数
// subcribe [channel] 一个或多个频道
subscribe souhu:tv
// unsubscribe [channel] 取消一个或多个频道
可以使用列表的阻塞拉取
mget、mset类似于一次发生多条get、set命令,节省网络开销
而hmset、hmget这样的命令是没有的,这个时候可以考虑pipeline
Jedis jedis = new ...
PipeLine pipeline = jedis.pipelined()
pipelined.hset...
...
pipeline.syncAndReturnAll()
原生m操作都是原子的,不会被插队
pipeline子命令可能会被其他命令插入到之间,但是返回结果是顺序的
集群中,pipeline每次只能作用到一个Redis节点
1.client发送命令
2.命令排队
3.执行命令
4.返回结果
当一条命令被定义为慢查询时,会进入到一个队列,先进先出固定长度的队列,队列保存在内存中,不会持久化
// 若想记录所有命令,设置为0,通常不会这么做,<0不记录任何命令
slowlog-log-slower-than=10000,单位是微秒
slowlog-max-len=128
// 配置方法
1.修改配置文件重启,不建议
2.动态配置
config set slowlog-max-len 1000 // 建议设置为1000
config set slowlog-log-slower-than 1000 // 单位是微妙,默认值是10000,建议1毫秒即1000
config rewrite
// 获取慢查询队列,n是多少个命令
slowlog get [n]
// 获取慢查询队列长度
slowlog leng
// 清空
slowlog reset
score可以重复,element不能重复
zadd key score1 element1 score2 element2...
zrem key element1...
zscore key element
zincrby key increScore element
zcard key
zrank key element // 获得元素排名
// zrange key start end [withscores]
zrange key 0 -1 withscores // 获得0到最后一个元素以及分值
zrangebyscore key minscore maxscore [withscores] //升序
zcount key minscore maxscore
// zremrangebyrank key start end
zremrangebyrank key 1 3
zremrangebyscore
score 可以是 timstamp(新度)、saleCount、followCount
zrevrank 跟zrank相反
...
zinterstore、zunionstore
sadd、srem
sinter、sdiff、sunion
// 将结果不仅返回,而且保存到destKey中
sinter、sdiff、sunion + store destKey
scard // 计算集合大小
sismember // 判断元素是否存在集合
srandmember key count // 随机取出count个元素
spop // 随机弹出一个元素
smembers // 取出所有元素,小心使用,用sscan代替
rpush key value1 value2 ...
// lpush
// 在list指定的值前|后插入newvalue
// 思考:当有两个value的时候
linsert key before|after value newValue
lpop key // rpop
// count > 0,删除count个value相等的项,从左到右
// count < 0,从右到左
// count = 0,所有
lrem key count value
// 只保留索引为 start ~ end的值
ltrim key start end
// end 为-1代表最后一个元素
lrange key start end
// i = -1时表示最后一个元素
lindex key i
llen key
lset key i newvalue
// 疑问:会阻塞其他命令吗
blpop key timeout // 阻塞等待
Stack = lpush + lpop
Queue = lpush + rpop
CappedCollection(固定容量) = lpush + ltrim
messageQueue = lpush + brpop
userinfo:1 // key
name = tom // filed = value
age = 40
Date = 201
hset key filed value
hget key field
hgetall key 所有filed和value
hvals key // 只返回所有value
hdel key field
hkeys key // 返回hash key对应所有的field
hexist key field // 判断key是否有field
hlen key // 获取key的field的数量
hmget key filed1 field2...
hmset key filed1 value1 filed2 value2
hincrby userinfo:1 pageviewcount count
get hget
set setnx hset hsetnx
del hde
setnx hsetnx key filed value
incrby hincrby
...
// 记录网站每个用户个人主页的访问量
incr pageview:userid
// 不管key是否存在,都设置
set key value
// key不存在才设置
setnx key value
// key存在才设置
set key value xx
// mget,mset,批量获取key,同时也是原子操作,比单次get,set效率高,减少网络传输时间
mget key1,key2...
mset key1 value1 key2 value2...
// 但是也不要同时获取或设置10W个key,要分批成1000更好
getset key newvalue // 设置新的值,并返回旧的值
append key value // 将value追加到旧的value
strlen hello // hello键对应的值得字节数
// incrbyfloat、getrange
// O(1)
// O(n)
mget、mset
开启服务的时候,先通过aof恢复,若aof没开启,则载入rdb文件恢复。
将内存数据异步保存到磁盘
// 快照
例如:MySQL的dump、Redis的RDB
// 日志
数据更新操作记录到日志
例如:mysql的binlog、Redis的AOF
// 触发机制-三种方式
// 方式一:save。同步,其他命令阻塞等待save执行
// 方式二:bgsave,异步,
可以正常响应客户端,但是fork出新的进程,更多开销,copy-on-write策略
// 方式三:自动,条件达到后触发
例如;save 900 10,900秒有10条改变则生成rdb文件
缺点:频繁生成rdb
配置:
× save 900 10 // 不推荐自动
dbfilename dump-${port}.rdb
dir /bigdiskpath // 大的硬盘路径
stop-writes-on-bgsave-error yes
rdbcompression yes // 压缩,建议开启
rdbchecksum yes //采用校验和
可能触发rdb文件生成方式:
1.主从复制,主会自动生成rdb文件
2.debug reload,不需要清空内存的重启
3.shutdown save
RDB现存问题:
耗时耗性能、不可控容易丢失数据
aof三种策略:always、everysec、no
先将命令放到缓冲区,而不是直接写入磁盘,再考虑策略将缓冲区的命令写入到磁盘
always:每条命令都写入到磁盘
everysec:每秒fsync到硬盘,默认是这个,可能会丢失一秒数据
no:使用策略根据操作系统决定
aof重写:文件太大
例子:
1.set hello world
set hello java
set hello hehe
重写:set hello hehe
2.incr count
incr count
重写:set count 2
3.rpush mylist a
rpush mylist b
rpush mylist c
rpush mylist a b c
4.过期失效的不写
触发aof重写的两种方式:
1.客服端发送命令bgrewriteaof,类似于bgsave完成rdb,都是fork出一个子进程去干
从当前数据库中的数据出发,而不是aof文件的改变
2.auto-aof-rewrite-min-size,文件重写需要的大小
auto-aof-rewrite-percentage,aof文件增长率,若为百分之百,则跟上一次大小一样时重写
配置:
appendonly yes
appendfilename “appendonly-${port}.aof”
appendfsync everysec
dir /找一个目录
auto-aof-rewrite-min-size 64mb
auto-aof-rewrite-percentage 100
1.建议关掉rdb,主从复制又会打开
2.若Redis只是当做缓存,数据丢失还可以从mysql加载,其实aof也可以关闭
3.预留部分内存给fork进程做aof文件重写之类的操作
4.建议使用everysec
Redis可以设置键的过期时间,键过期之后,没有从内存清除,而是存在过期字典中,依然占用内存,Redis提供了三种清除策略:
1.定时删除,设置过期键同时设置一个定时器,对键执行删除操作。
缺点:对CPU造成压力,当很多键同时删除时候
2.惰性删除,放任键过期不管,每次获取键时,检查键是否过期,若过期才删除
缺点:若大量键过期,又没有业务访问,造成内存浪费
3.定期删除:每隔一段时间,对数据库进行一次检查,删除里面过期的键,至于删除多少键,以及检查多少个数据库,由算法决定。
默认100毫秒检查是否有过期的key,有过期的key则删除,这个检查是随机抽查配置个数的key,而不是所有key
难点:频率难以设定
淘汰策略:配置maxmemory设置,当Redis内存使用达到最大内存使用值时,采取一定策略进行内存释放,不单单是过期的数据,而是所有数据
volatile-lru -> 淘汰上次使用时间最早的且使用次数最少的key,只淘汰设定了有效期的key
allkeys-lru -> 所有的key都可以被淘汰
lru默认随机挑5个键,并且从中选取一个最近最久未使用的key进行淘汰,可以通过
maxmemory-samples来设置Redis需要检查key的个数
volatile-lfu -> 驱逐使用频率最少的键,配置了过期时间的key
allkeys-lfu
volatile-random -》 随机淘汰数据,配置了过期时间的key
allkeys-random
volatile-ttl -> 只淘汰剩余有效期最短的key
noeviction -> 不删除任何数据,不建议使用,内存不够,返回错误
单机的问题:
1.机器故障
2.容量瓶颈
3.QPS瓶颈
以下只讨论高可用
数据副本
扩展读性能
数据流向只能是单向,master》slave
实现主从复制两种方式
1.slaveof命令
从Redis执行
slaveof 主Redis的ip+port // 成为从节点
slaveof no one
2.配置,需要重启
slaveof ip port
slave-read-only yes
不想成为从节点了:slaveof no one
没有Redis Sentinel时,解决服务器故障
主发生故障:
选取一台从成为新的主
从发生故障:
将读压力迁移到另一台从
sentinel monitor myMaster ip port 2
// ping30秒还不通,主观下线
sentinel down-after-milliseconds mymaster 300000
Redis Sentinel
1)每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他 Sentinel 实例发送一个PING命令。
2)如果一个实例(instance)距离最后一次有效回复PING命令的时间超过 own-after-milliseconds 选项所指定的值,则这个实例会被Sentinel标记为主观下线。
3)如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master的确进入了主观下线状态。
4)当有足够数量的Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态,则Master会被标记为客观下线。
5)在一般情况下,每个Sentinel 会以每10秒一次的频率向它已知的所有Master,Slave发送 INFO 命令。
6)当Master被Sentinel标记为客观下线时,Sentinel 向下线的 Master 的所有Slave发送 INFO命令的频率会从10秒一次改为每秒一次。
7)若没有足够数量的Sentinel同意Master已经下线,Master的客观下线状态就会被移除。 若 Master重新向Sentinel 的PING命令返回有效回复,Master的主观下线状态就会被移除。
8)确认master已下线后:
9)选举出一个sentinel作为领导,因为一个sentinel就能完成切换
10)通过这个sentinel选举出一个slave作为新的mater,通知其他slave成为新mater的slave,通知客户端主从变化
11)等待master复活成为新的mater的slave
一套sentinel可监控多套master-slave,用master-name作为标识
new JedisSentinelPool(matername,sentinelSet)
客户端并不是操作的sentinel,其实还是操作的Redis-server
master才有客观下线,slave主观下线就好了
sentinel只是配置中心,而不是代理
1.每10秒每个sentinel对master和slave执行info
目的:发现slave节点,确认主从关系
2.每2秒每个sentinel通过master节点的channel交换信息(pub/sub)
交换对节点的看法和自身的信息,通过_sentinel_:hello频道交互
3.每一秒每个sentinel对其他sentinel和Redis执行ping
心跳检测,失败判定的依据
sentinel内部选举出一个节点完成故障转移
Asentinel向B、C发送命令要求将他设置为领导者
B若没有收到或同意C发来的命令,那么将票投给A,同理C
如果A发现自己的票数超过sentinel集合半数且超过quorum = 2,那么A将成为领导者
如果此过程中多个节点成为领导者,那么等待一段时间重新选举
服务端完成故障转移之后 pub/sub 客户端如何操作
重新初始化:遍历所有sentinel,找出一个可用的,然后获取新master的连接
从节点下线怎么处理:
自定义一个类似于JedisSentinelPool的客户端,订阅三个消息:
从节点晋升为主节点、原主节点降为从节点、主观下线
1.跟高并发量
2.更大的数据量
3.更宽的网络流量
顺序分区:例如:Bigtable、hbase
容易发生数据倾斜,例如前1-100的用户更加活跃
哈希分区:memcache、Rediscluster
节点取余分区、一致性hash分区、虚拟槽分区(Rediscluster)
节点取余分区:
问题:新增节点导致大量数据迁移
一致性哈希:
所有节点围成一个圈,顺时针寻找自己对应的节点
缺点:依然会有数据迁移,若新增一个节点5在1和2之间,5节点只会分摊2节点的数据量,对其他节点不公平
虚拟槽:
16384个槽,0~16383,每个节点管理部分槽,每个节点之间互相通信,知道节点负责哪个槽
CRC16(KEY)&16383找到自己对应的槽
节点有一个配置属性:cluster-enabled=true
A meet B,B响应A
A meet C,C响应A
这个时候A、B、C就是通的了,能够互相感知到对方,可以交换消息,所有节点共享消息
指派槽:
例如:给A指派0-8000,给B指派8000-13000,给C指派13001-16383
原生命令的安装:
1.配置开启节点
cluster-enabled yes
cluster-config-file nodes-${port}.conf
cluster-node-timeout 15000 // 15秒
cluster-require-full-coverage yes //若为yes,则需要集群所有节点健康才提供服务,一般为no
启动6个节点:7000~7005
2.meet
7000 cluster meet 7001
7000 cluster meet 7002
...
7000 cluster meet 7005
meet之后,所有节点都能相互感知
3.指派槽
以第一台7000为例:
cluster addslots {0、 1、 。。。5461}
16384个槽都要分配完
4.主从
将7003节点作为7000节点的从
7003 cluster replicate ${node-id-7000}
官方工具安装:rubby
安装ruby
安装Redis-trib
可视化工具安装:
...
扩容集群:
1.准备新节点
2.加入集群
cluster meet完成
加入新节点的作用
A。迁移槽和数据实现扩容,作为主节点
一:槽迁移计划:
平均槽数量,例如之前三个节点 ,每个平均分摊到5460个槽,现在加入一个新节点,
每个节点应该分摊4096个槽,然后三个老节点将部分槽拿出来凑成4096个给新节点
二:迁移数据
B。作为从节点实现故障转移
收缩集群:
1.下线迁移槽
迁移槽到其他节点,通知其他节点忘记该节点,因为该节点已下线
2.忘记节点
3.关闭节点
集群客户端路由:
moved重定向
ask重定向
smart客户端
moved重定向
客户端发送命令到任意节点
根据key计算槽和槽对应的节点
若计算结果就是在本节点,那么直接返回结果,槽命中
若槽不是在本节点,那么回复moved让客户端重定向到正确的节点
ask重定向:
move和ask的区别:
moved,槽已确定迁移
ask,槽还在迁移中
smart客户端原理
1.从集群中选一个可运行的节点,使用cluster slots初始化槽和节点映射
2.将结果保存到本地,为每个节点创建jedispool
3.执行命令,jediscluster缓存了key -》slot -》node关系
一般执行成功,
出错的话:
4.随机选取一个节点,发送命令,一般不能命中,返回moved
5。重新初始化slot-》node缓存,再次发送命令,一般成功
若命令发送超过5次还不成功,则抛出too many cluster redirection
jediscluster基本使用:
Set<HostAndPort> nodeList = new HashSet<HostAndPort>();
nodeList.add(一个cluster节点);
...直到将所有cluster节点添加完毕
JedisCluster jediscluster = new (noedList,timeOut,poolConfig);
jediscluster.执行命令
使用技巧:单例
批量操作:
meget、mset必须在一个槽,这个条件相当苛刻
四种思路:
1.串行mget、mset,即串行get、set每一个key
2.
故障发现
故障恢复
1.加速读写
2.降低后端负载
例如:前端缓存降低后端服务器负载,业务使用Redis降低mysql负载
大量写合并为批量写,例如,先Redis累加计数器,再批量写入db
成本:
3.数据不一致
和更新策略有关
4.代码维护成本,多了一层缓存逻辑
5.运维成本,Rediscluster
1.lru、lfu、FIFO算法剔除,设置maxmemory
2.超时剔除,expire
3.主动更新,订阅消息,发生变化的时候接受到消息主动更新,不是强一致,而是最终一致
建议:超时剔除和主动更新结合,为什么还需要超时剔除呢,如果发送消息的逻辑出意外,超时剔除保证最终也会得到更新
缓存粒度问题:
是缓存部分字段还是所有字段
原因:
1.业务代码自身问题
2.恶意攻击,爬虫等等
如何发现:
解决方法:
缓存空对象
布隆过滤器
缓存空对象:
问题:需要更多的键,设置键的过期时间;
缓存层和存储层短期不一致。可能出现这样的情况,我的存储层短暂出现了问题,这个时候依然返回的是空,数据不一致
伪代码:
cacheValue = cache.get(key)
if(value为空){
storageValue = storage.get(key)
cache.set(key,storageValue)
if(从storage中查出的value是null){
cache.expire(key,60*5)
}
}else {
return cacheValue;
}
布隆过滤器:检索一个元素是否存在一个集合中
问题;现有50亿个电话号码,现在有10万个电话号码,快速准确判断这10万电话号码是否存在于50亿中
类似问题,垃圾邮件过滤
x通过随机映射函数f1、f2、f3,将二进制数组的三处位置将0改为1
y也同样如此
现在要查询z是否在x,y的集合里,z若通过三个映射函数映射到的位置有一处不为1,则z不存在集合里
guava:布隆过滤器工具,单机布隆过滤器
Redis布隆过滤器
Redis分布式布隆过滤器
待续
无底洞问题:更多的机器不能保证更高的性能
问题:一万个人同时查询数据,先从缓存查询,然后10000个人都发现缓存没数据,然后10000个人同时查询数据库,造成数据库崩溃
解决1:查询数据库之前加锁,只允许一个人查询数据库,然后再判断缓存是否有数据,若没数据再查询数据库
namespace必须对应相应的mapper.java,每一个mapper.xml都会对应一个mapper.java,使用namespace关联起来
使用alias别名功能
方法名对应id,参数值类型对应parameterType=“int”,返回值类型对应resultType例如User,返回值类型是多表查询的结果时,需要自定义resultMap,这个时候就不是resultType了,而是resultMap
取的是方法参数传进来的变量名称,若参数是对象例如User,则取user中的属性#{username}
单表查询不需要自定义resultMap,多表查询出现集合,或者一个对象的属性引用另一个对象的时候就需要自定义resultMap了
result、collection、association
id:被引用,例如被select的resultMap属性引用
type:指定一个java类型
property指的是Java类的属性,column指的是数据库列名
id、result、association、collection:都有property,因为必须对应一个pojo的属性
id、result会有column对应数据库的列,collection和association不会有,因为他们对应的是一个java对象,对应的是多个列
id和result对应是一列和一个属性,所以在java这边会有一个javaType属性,在数据库那边有一个jdbcType属性,而association只有java这边的类型,所以会有一个javaType属性,collection也只有java这边的类型,但是是一个ofType的属性
当属性只是一个pojo对象时,选择association,当属性是一个集合时,选择collection,他们里面都使用id和result标签
表示类型的时候,id和result使用jdbcType,association使用javaType,collection使用ofType
collection和association只有property属性,不像id和result既有property属性还有column属性
例如一个user对应多个post,一对多则是一个user对象里面有一个post集合,多对一则是一个post对象里有一个user对象;一对多时,resultMap里面会用到collection子标签表示集合属性,多对一时,resultMap里面会用到association表示另一个对象属性
themes是list 的属性名,content是数据库字段名
放在最下面,放在result下
parameterType是一个java类型,parameterMap是一个定义的parameterMap标签,parameterMap标签有一个type属性指向一个java类型,但是依然要选择java类型的部分属性作为子元素,parameterMap使用parameter作为子标签,parameter有一个property属性,作用就不言而喻了
逻辑分页是我们的程序在显示每页的数据时,首先查询得到表中的1000条数据,然后根据当前页的“页码”选出其中的100条数据来显示。
物理分页是程序先判断出该选出这1000条的第几条到第几条,然后数据库根据程序给出的信息查询出程序需要的100条返回
limit 0 10,指的是从0行开始的10条记录
limit 5 20,指的是从5行开始的20条记录
分页思想是查出所有,减掉前面
if标签有一个test属性,属性值为true或false
AND author_name like #{author.name}
如果这些条件没有一个能匹配上将会怎样?最终这条 SQL 会变成这样
SELECT * FROM BLOG
WHERE
修改:
where 元素知道只有在一个以上的if条件有值的情况下才去插入“WHERE”子句。而且,若最后的内容是“AND”或“OR”开头的,where 元素也知道如何将他们去除
where id = #{id}
update Author
username=#{username},
password=#{password},
email=#{email},
bio=#{bio}
where id=#{id}
set标签好处:帮助省略无关逗号
map、数组
item,index,collection,open,separator,close
item表示集合中每一个元素进行迭代时的别名,
index指 定一个名字,用于表示在迭代过程中,每次迭代到的位置,
open表示该语句以什么开始,
separator表示在每次进行迭代之间以什么符号作为分隔 符,
close表示以什么结束
List ids = new ArrayList();
ids.add(1);
ids.add(2);
ids.add(3);
ids.add(6);
ids.add(7);
ids.add(9);
Map params = new HashMap();
params.put("ids", ids);
params.put("title", "中国");
可以看到collection=“ids”,遍历的是map其中一个为list的key,而不是map的keyset
collection=“array”
collection=“list”
in (a, b, c)这种需要
方式一出错:name like “%” #{xxx, jdbcType = varchar} “%”
方式二出错:like ‘%${xxx}%’
方式三:LIKE CONCAT(’%’,#{poetryName},’%’)
因为#{…}解析成sql语句时候,会在变量外侧自动加单引号’ ',所以这里 % 需要使用双引号" ",不能使用单引号 ’ ’
where 1 = 1 and name = #{name} ,使用where标签就不用加 1=1了
username = #{username},
sex = #{sex},
birthday = #{birthday},
set标签解决了逗号问题
@Param(“account”)String account
param没什么用,可以用Map
要有,属性值为 parameterType=“java.util.Map”
#会预编译,再填入值,$是拼接字符串执行
select ${column} from ${table} where id = #{id}
上述情况就必须使用$拼接了,如果使用#,查出来的是填入的别名,甚至报错
basemapper
定义代码片段,然后由引用
例如:
SELECT
FROM
apc
WHERE id = #{id}
apc.id, apc.name, apc.sequence, apc.version_code, apc.status, apc.create_time , apc.type
insert标签加入下面两个属性
keyProperty=“id” useGeneratedKeys=“true”
SQLsession不同
SQLsession相同,查询条件不同
SQLsession相同,两次查询之间,有增删改
手动清空缓存
基于namespace
SQLsession关闭,数据转移到二级缓存中
先从一级缓存中找,一级缓存关闭才去二级缓存
一级缓存还没有关闭
A1 = session1.getMapper(A)
A2 = session2.getMapper(A)
A1.getElementById(1)
A2.getElementById(2)
session1.close
session2.close
两个不同的session,用不到一级缓存
两个session都没关闭,也用不到二级缓存
所以会执行两条SQL
全局配置,setting-cachedenabled=true
在每个mapper.xml中加cache标签
在select标签使用usercache = true
先进先出
软引用
弱引用
true:只读,mybatis认为用户只读,会直接将引用交给用户,不安全,速度快
false:非只读,mybatis会利用序列化技术clone一份新的数据,安全,低效,pojo需要实现序列化接口
标签中默认带flushcache = true
二级缓存也会清空
二级缓存
先看二级缓存,再看一级缓存
mybatis提供了一个cache接口,自己实现put,get等方法,默认使用perpetualcache
由mybatis实现的,需要导入mybatis的这个适配器,在mapperXML的cache标签配置
insert插入一条记录,发现这条记录重复则插入失败
replace插入一条记录,若发现重复,则先删除重复的,再插入
重复判断是根据主键或唯一索引判断
drop:drop table 表名
删除内容和定义,并释放空间。执行drop语句,将使此表的结构一起删除
truncate (清空表中的数据):truncate table 表名
删除内容、释放空间但不删除定义(也就是保留表的数据结构)。与drop不同的是,只是清空表数据而已。
truncate不能删除行数据,虽然只删除数据,但是比delete彻底,它只删除表数据。
delete:delete from 表名 (where 列名 = 值)
与truncate类似,delete也只删除内容、释放空间但不删除定义;但是delete即可以对行数据进行删除,也可以对整表数据进行删除。
1.delete语句执行删除的过程是每次从表中删除一行,并且同时将该行的删除操作作为事务记录在日志中保存,以便进行进行回滚操作。
2.执行速度一般来说:drop>truncate>delete
3.delete语句是数据库操作语言(dml),这个操作会放到 rollback segement 中,事务提交之后才生效;如果有相应的 trigger,执行的时候将被触发。
4.truncate、drop 是数据库定义语言(ddl),操作立即生效,原数据不放到 rollback segment 中,不能回滚,操作不触发trigger。
5.id为自增时,delete from 整个表,然后再插入记录,id值与之前id保持连续,而truncate重新从1开始
方式一:group by ‘name’,不行这样只能查出不同的名字和相应组上聚合结果
方式二:
select id,name
from student
where name in (
select name from student group by name having(count(*) > 1)
) order by name;
先利用group by查出重复的名字,再查出重复名字的其他信息
select sid,avg(score) as avg_score
from student_course
group by sid having(avg_score<60);
根据每个学生分组,查出其平均分,然后having筛选出平均分
select distinct sid
from student_course
where sid not in (select sid from student_course where score < 80);
查询出有课程分数低于80的学生,再取反
select sid,sum(score) as sum_score
from student_course
group by sid
order by sum_score desc
limit 1;
排序,查第一条记录
select * from student_course
where cid=1 and score = (
select score from student_course
where cid = 1
group by score
order by score desc limit 2,1
);
将分数排序,使用limit 查出第三高的分数,再查出等于这个分数的学生
select * from student_course as x
where score>=(
select max(score) from student_course as y
where cid=x.cid
);
遍历每条记录,当score大于等于这条记录的课程的最大分数返回
select * from student_course x
where 2>(
select count(*) from student_course y
where y.cid=x.cid and y.score>x.score
)
order by cid,score desc;
对于每一个分数,如果同一门课程下只有0个、1个分数比这个分数还高,那么这个分数肯定是前2名之一
年 季度 销售
1991 1 11
1991 2 12
1991 3 13
1991 4 14
1992 1 21
1992 2 22
1992 3 23
1992 4 24
要求:写一个SQL语句查询出如下所示的结果。
年 一季度 二季度 三季度 四季度
1991 11 12 13 14
1992 21 22 23 24
SQL:
select 年,
sum(case when 季度=1 then 销售量 else 0 end) as 一季度,
sum(case when 季度=2 then 销售量 else 0 end) as 二季度,
sum(case when 季度=3 then 销售量 else 0 end) as 三季度,
sum(case when 季度=4 then 销售量 else 0 end) as 四季度
from sales group by 年;
范围
例如找出所以以h到k开头的名字
不会,只有全值匹配、精确匹配第一列,范围匹配第二列、精确匹配一二列,范围匹配第三列
精确匹配索引所有的列查询
哈希索引在mysql中只有memory引擎支持
对于非常小的表,简单的全表扫描更高效
中大型表,非常高效
建立和使用索引的代价将随之增长,建议分区
前缀索引
前缀索引也是B树索引
最好的情况也只能是一星索引,查询永远只能发挥出一个列的效率
当不需要考虑排序或分组时,将选择性最高的列放在前面,相同值越多,选择性越低
不为空
会增加维护成本
整型
内建类型
计划在列上建立索引,避免设计成可为空的列
秒
timestamp只使用datetime一半的存储空间,具有特殊的自动更新能力,,建表时,可以设置insert或update自动更新
但是timestamp允许的时间范围要小
32位与64位,
占4字节与8字节
unsigned,负值
CPU直接支持原生浮点运算,decimal的高精度运算是mysql自身实现的
sout(1.2-1)结果不是0.2,而是0.19999996
bigint,,例如存储1.5元,可以乘以10等于15角,而1.55,可以乘以100,等于155厘
使用varchar:字符串列长度比平均长度大很多,列的更新很少
char适合所有的字符串接近同一个长度,或者很短的字符串
varchar是变长,只分配必要的空间,比char更节省空间,当行变长时,如果没有足够的空间,会导致分裂页,所以需要更新少才用varchar
2038
9999
使用bigint,转换一下
也可以使用double存储秒之后的小数部分
每小时统计一次当前小时的消息数,将过去23小时消息数与现在未完成的消息数相加既得
可以添加一百行数据,每次随机选择一个槽进行更新,要获得统计结果只需要聚合查询一下即可
sum(cnt),将所有槽相加
放弃外键,使用冗余字段
统计24小时的登录用户数量:每小时统计一次,将前23小时加起来,再加上未结束的这个小时的数量;
多个事务更新计数器:可以增加多行作为计数器,最终统计计数器之和
mybatis的一级缓存,二级缓存
Redis缓存
本地缓存