NewLife/X

dependabot[bot] 编写于 2024-05-15 07:16:03 GitHub 提交于 2024-05-15 07:16:03
ee863ae
CacheBase.cs Loading... Loading... 0001-01-01 08:00:00
DataCache.cs Loading... Loading... 0001-01-01 08:00:00
DbCache.cs Loading... Loading... 0001-01-01 08:00:00
EntityCache.cs Loading... Loading... 0001-01-01 08:00:00
FieldCache.cs Loading... Loading... 0001-01-01 08:00:00
IEntityCache.cs Loading... Loading... 0001-01-01 08:00:00
readme.md Loading... Loading... 0001-01-01 08:00:00
SingleEntityCache.cs Loading... Loading... 0001-01-01 08:00:00
readme.md
# 缓存架构设计 核心原则:`读多写少的数据,缓存起来,减少数据库访问,提升性能` ## 数据表行数缓存 数据表行数缓存`Meta.Count`,`代表了相对准确的表行数,在1000行以内精确`,1000行以上从索引表查询,精确度由数据库保证。 *多年使用经验来看,`SQLite/MSSQL/MySql/Oracle`索引表里面的表行数相当准确* 查询数据表行数一般用`Student.FindCount(where)`,还可以指定条件。 实际使用中,用得最多的是查整表行数,供分页等业务逻辑使用。 `FindCount()`执行的是`Select Count(*) From Table`,在很多没有预热的大数据表(百万千万级)里面,该语句执行起来非常慢,往往需要超过1秒的时间,用户体验很不好。 快速查询`QueryCountFast`从索引表读取表行数。 本框架内,大量使用表行数来决定不同的优化策略。 ## 实体缓存 实体缓存就是`整表缓存那些读取很多修改极少的数据`,用于系统参数表、栏目分类表等。 实体类内使用'Meta.Cache.Entities'即可触发使用实体缓存,内部将执行一次查询('Select * From Table')加载整表数据为实体列表。 `Meta.Cache.Entities`就是这个实体列表,使用缓存实际上就是在这个列表上执行`Find`/`FindAll`操作。 基于性能考虑,建议单表数据小于1000行时使用,大于10000时坚决不要使用。 工具生成的实体业务类代码经常可以看到如下代码: ```csharp public Student FindByID(Int32 id) { if (id <= 0) return null; if (Meta.Count >= 1000) return Find(__.ID, id); else // 实体缓存 return Meta.Cache.Entities.Find(__.ID, id); } ``` 因此,`FindByID`在该表数据小于1000时,其实是使用实体缓存。 缓存默认过期时间`60秒`,过期后使用仍然是马上返回旧数据,同时开启异步查询更新缓存。 任何添删改等改动数据库的操作,都将会让缓存马上过期,并启动异步更新。 任何添删改操作,都将实时修改缓存,即使在异步更新完成之前,从缓存拿到的也是最新数据。除非有其它进程更新了数据表,此时需要等缓存的异步更新操作完成才能得到最新数据。 ## 单对象缓存 单对象缓存就是`以主键字典逐行缓存数据`,常用于需要单行数据查询的场合,比如用户表等。 可以看做是一个字典,以主键为Key,实体对象为Value。每次查询先在字典里面搜索,找到则返回,找不到则去数据库查,缓存起来后再返回。 实体类内使用`Meta.SingleCache[key]`即可启用单对象缓存。 ```csharp public Student FindByID(Int32 id) { if (id <= 0) return null; return Meta.SingleCache[id]; } ``` 缓存默认过期时间`60秒`,过期后更新。 任何添删改等改动数据库的操作,都将会清空缓存。 ```sequence{theme="simple"} 业务代码->对象缓存: 主键 note over 对象缓存: 缓存不存在 对象缓存-->>数据库: 查询数据库 note over 对象缓存: 已存在 对象缓存->业务代码: 返回对象 note over 对象缓存: 已过期 对象缓存-->>数据库: 异步更新 定时器-->>对象缓存: 清理过期 ``` ```sequence{theme="simple"} 业务代码->对象缓存: 主键 note over 对象缓存: 缓存不存在 对象缓存-->>数据库: FindKey 数据库-->>对象缓存: 更新主键 数据库-->>对象缓存: 更新从键 业务代码->对象缓存: 从键 note over 对象缓存: 缓存不存在 对象缓存-->>数据库: FindSlaveKey 数据库-->>对象缓存: 更新主键 数据库-->>对象缓存: 更新从键 ``` ## 缓存更新策略 在没有使用事务时,对数据表的任何添删改,将会让该表的实体缓存马上过期,以及清空单对象缓存。 使用事务时,每一个添删改操作仅修改缓存,直到事务提交或回滚才清空缓存。 ## 特别优化 `SQLite`没有索引表供快速查询表行数,而直接`Select Count`又慢,因此框架针对`SQLite`进行特别优化。 获取表行数时,如果有自增字段,首先获取其最大值临时充当表行数,然后启动异步查询`Select Count`以获取精确的表行数。 因此,`SQLite`使用`Meta.Count`时,第一次得到的数据可能有偏差,一会后即可得到精确数据。时间的长度主要由数据表大小决定,一百万数据大概需要几百毫秒。该偏差完全可以通过系统启动时进行系统预热来对冲掉。