深入浅出MongoDB(六)

news2024/10/9 15:48:51

深入浅出MongoDB(六)

文章目录

    • 深入浅出MongoDB(六)
      • 分析查询性能
      • 原子性和事务
      • 字段名称带句点和美元符号
      • 查询计划

分析查询性能

  1. mongodb compass提供解释计划标签页,其中显示有关查询性能的统计信息。这些统计信息可用于衡量查询是否以及如何使用索引。
  2. cursor.explain("executionStats")db.collection.explain("executionStats")方法提供了有关查询性能的统计信息。
  3. 评估查询性能
# 往集合inventory中添加文档
db.inventory.insertMany([
    {_id: 1, item: 'f1', type: 'food', quantity: 500},
    {_id: 2, item: 'f2', type: 'food', quantity: 100},
    {_id: 3, item: 'p1', type: 'paper', quantity: 200},
    {_id: 4, item: 'p2', type: 'paper', quantity: 150},
    {_id: 5, item: 'f3', type: 'food', quantity: 300},
    {_id: 6, item: 't1', type: 'toys', quantity: 500},
    {_id: 7, item: 'a1', type: 'apparel', quantity: 250},
    {_id: 8, item: 'a2', type: 'apparel', quantity: 400},
    {_id: 9, item: 't2', type: 'toys', quantity: 50},
    {_id: 10, item: 'f4', type: 'food', quantity: 75}
])
# 无索引查询quantity字段值在100~200之间的文档
db.inventory.find({quantity: {$gte: 100, $lte: 200}})
# 查看选定的查询计划,将cursor.explain("executionStats")游标方法连接到find命令末尾
db.inventory.find({quantity: {$gte: 100, $lte: 200}}).explain('executionStats')
# explain方法返回结果如下
# queryPlanner.winningPlan.stage是COLLSCAN表示会进行集合扫描,mongod必须对整个集合的文档进行逐份扫描才能识别结果,此项操作通常成本很高,可能会降低查询速度。
# executionStats.nReturned是3,表示获胜查询计划返回三个文档。
# executionStats.totalKeysExamined是0,表示该查询未使用索引。
# executionStats.totalDocsExamined是10,表示mongodb必须扫描10个文档以查找三个匹配的文档。
[
  {
    "command": {
      "find": "inventory",
      "filter": {
        "quantity": {
          "$gte": 100,
          "$lte": 200
        }
      },
      "$db": "test"
    },
    "executionStats": {
      "executionSuccess": true,
      "nReturned": 3,
      "executionTimeMillis": 22,
      "totalKeysExamined": 0,
      "totalDocsExamined": 10,
      "executionStages": {
        "stage": "COLLSCAN",
        "filter": {
          "$and": [
            {
              "quantity": {
                "$lte": 200
              }
            },
            {
              "quantity": {
                "$gte": 100
              }
            }
          ]
        },
        "nReturned": 3,
        "executionTimeMillisEstimate": 0,
        "works": 12,
        "advanced": 3,
        "needTime": 8,
        "needYield": 0,
        "saveState": 0,
        "restoreState": 0,
        "isEOF": 1,
        "direction": "forward",
        "docsExamined": 10
      }
    },
    "explainVersion": "1",
    "ok": 1,
    "queryPlanner": {
      "namespace": "test.inventory",
      "indexFilterSet": false,
      "parsedQuery": {
        "$and": [
          {
            "quantity": {
              "$lte": 200
            }
          },
          {
            "quantity": {
              "$gte": 100
            }
          }
        ]
      },
      "maxIndexedOrSolutionsReached": false,
      "maxIndexedAndSolutionsReached": false,
      "maxScansToExplodeReached": false,
      "winningPlan": {
        "stage": "COLLSCAN",
        "filter": {
          "$and": [
            {
              "quantity": {
                "$lte": 200
              }
            },
            {
              "quantity": {
                "$gte": 100
              }
            }
          ]
        },
        "direction": "forward"
      },
      "rejectedPlans": []
    },
    "serverInfo": {
      "host": "30fb3e0dfa72",
      "port": 27017,
      "version": "5.0.5",
      "gitVersion": "d65fd89df3fc039b5c55933c0f71d647a54510ae"
    },
    "serverParameters": {
      "internalQueryFacetBufferSizeBytes": 104857600,
      "internalQueryFacetMaxOutputDocSizeBytes": 104857600,
      "internalLookupStageIntermediateDocumentMaxSizeBytes": 104857600,
      "internalDocumentSourceGroupMaxMemoryBytes": 104857600,
      "internalQueryMaxBlockingSortMemoryUsageBytes": 104857600,
      "internalQueryProhibitBlockingMergeOnMongoS": 0,
      "internalQueryMaxAddToSetBytes": 104857600,
      "internalDocumentSourceSetWindowFieldsMaxMemoryBytes": 104857600
    }
  }
]
# 带索引的查询,为quantity字段添加索引
db.inventory.createIndex({quantity: 1})
# 查看查询计划的统计信息
db.inventory.find({quantity: {$gte: 100, $lte: 200}}).explain('executionStats')
# explain方法结果如下
# queryPlanner.winningPlan.stage是IXSCAN,表示使用索引。
# executionStats.nReturned是3,表示获胜查询计划返回三个文档。
# executionStats.totalKeysExamined是3,表示扫描到了3个索引条目。检查的密钥数量与返回的文档数量相匹配,只需要检查索引建就可以返回结果。
# executionStats.totalDocsExamined是3,表示mongodb扫描了3个文档。
[
  {
    "command": {
      "find": "inventory",
      "filter": {
        "quantity": {
          "$gte": 100,
          "$lte": 200
        }
      },
      "$db": "test"
    },
    "executionStats": {
      "executionSuccess": true,
      "nReturned": 3,
      "executionTimeMillis": 86,
      "totalKeysExamined": 3,
      "totalDocsExamined": 3,
      "executionStages": {
        "stage": "FETCH",
        "nReturned": 3,
        "executionTimeMillisEstimate": 19,
        "works": 4,
        "advanced": 3,
        "needTime": 0,
        "needYield": 0,
        "saveState": 1,
        "restoreState": 1,
        "isEOF": 1,
        "docsExamined": 3,
        "alreadyHasObj": 0,
        "inputStage": {
          "stage": "IXSCAN",
          "nReturned": 3,
          "executionTimeMillisEstimate": 19,
          "works": 4,
          "advanced": 3,
          "needTime": 0,
          "needYield": 0,
          "saveState": 1,
          "restoreState": 1,
          "isEOF": 1,
          "keyPattern": {
            "quantity": 1
          },
          "indexName": "quantity_1",
          "isMultiKey": false,
          "multiKeyPaths": {
            "quantity": []
          },
          "isUnique": false,
          "isSparse": false,
          "isPartial": false,
          "indexVersion": 2,
          "direction": "forward",
          "indexBounds": {
            "quantity": ["[100, 200]"]
          },
          "keysExamined": 3,
          "seeks": 1,
          "dupsTested": 0,
          "dupsDropped": 0
        }
      }
    },
    "explainVersion": "1",
    "ok": 1,
    "queryPlanner": {
      "namespace": "test.inventory",
      "indexFilterSet": false,
      "parsedQuery": {
        "$and": [
          {
            "quantity": {
              "$lte": 200
            }
          },
          {
            "quantity": {
              "$gte": 100
            }
          }
        ]
      },
      "maxIndexedOrSolutionsReached": false,
      "maxIndexedAndSolutionsReached": false,
      "maxScansToExplodeReached": false,
      "winningPlan": {
        "stage": "FETCH",
        "inputStage": {
          "stage": "IXSCAN",
          "keyPattern": {
            "quantity": 1
          },
          "indexName": "quantity_1",
          "isMultiKey": false,
          "multiKeyPaths": {
            "quantity": []
          },
          "isUnique": false,
          "isSparse": false,
          "isPartial": false,
          "indexVersion": 2,
          "direction": "forward",
          "indexBounds": {
            "quantity": ["[100, 200]"]
          }
        }
      },
      "rejectedPlans": []
    },
    "serverInfo": {
      "host": "30fb3e0dfa72",
      "port": 27017,
      "version": "5.0.5",
      "gitVersion": "d65fd89df3fc039b5c55933c0f71d647a54510ae"
    },
    "serverParameters": {
      "internalQueryFacetBufferSizeBytes": 104857600,
      "internalQueryFacetMaxOutputDocSizeBytes": 104857600,
      "internalLookupStageIntermediateDocumentMaxSizeBytes": 104857600,
      "internalDocumentSourceGroupMaxMemoryBytes": 104857600,
      "internalQueryMaxBlockingSortMemoryUsageBytes": 104857600,
      "internalQueryProhibitBlockingMergeOnMongoS": 0,
      "internalQueryMaxAddToSetBytes": 104857600,
      "internalDocumentSourceSetWindowFieldsMaxMemoryBytes": 104857600
    }
  }
]

原子性和事务

  1. 在mongodb中写入操作在单个文档级别上是原子性的,即使该操作修改单个文档中的多个嵌入文档也是这样的。
  2. 当单个写操作比如db.collection.updateMany()修改了多份文档,则每份文档的修改都是原子性的,但整个操作不是原子性的。在执行多文档写入操作时,无论是通过单次写入操作还是多次写入操作,其他操作都可能会交错进行。
  3. 并发控制允许多个应用程序同时运行,而不会造成数据不一致或数据冲突。对文档的findAndModify操作是原子性操作,如果查找条件与文档匹配,则对该文档执行更新。在当前更新完成之前,该文档的并发查询和其他更新不会受影响。
db.myCollection.insertMany([
	{_id: 0, a: 1, b: 1},
	{_id: 1, a: 1, b: 1}
])
# 两个findAndModify操作同时运行后,保证两份文档中的a和b都设置为2
db.myCollection.findAndModify({
	query: {a: 1},
	update: {$inc: {b: 1}, $set: {a: 2}}
})
  1. 我们可以对字段创建唯一索引,以使其仅拥有唯一值,可以防止插入和更新操作创建重复数据;还可以对多个字段创建唯一索引,以确保字段值的组合是唯一的。

字段名称带句点和美元符号

  1. mongodb 5支持以美元为前缀或包含句点的字段名称的支持。在大多数情况下,使用类似字段名称存储的数据不能直接访问,我们需要在访问字段的查询中使用诸如$getField$setField$literal等辅助方法。
  2. 插入操作
# 允许以$为前缀的字段作为插入操作的顶层和嵌套字段名
db.sales.insertOne({
	"$price": 50.00,
	"quantity": 30
})
# 附带$前缀的字段可以在使用其他保留字的插入操作中使用
# $inc一类的运算符名称以及类似id、db和ref的词可用作字段名称
db.books.insertOne({
	'$id': 'h1999-09',
	'location': {
		'$db': 'novels',
		'$ref': '2024100712',
		'$inc': true
	}
})
# 在更新或插入(upsert)期间创建新文档的更新被视为insert而非update,可以接受$为前缀的字段
# 如果更新中的match部分选择的是现有文档,类似的更新操作可能会引发错误
# upsert为true,如果集合中没有指定日期的文档,会插入一个新文档,如果与现有文档匹配则更新失败
db.expenses.updateOne(
	{'date': '2024-10-07'},
	{$set: {
		'phone': 25.12,
		'$hotel': 320.15
	}},
	{upsert: true}
)
  1. 文档替换更新
# 更新操作符需要用新文档替换现有文档或者修改这些字段,通过更新替换时,不得以$前缀字段作为顶级字段名称
# 可以使用替换现有文档的更新操作符来修改address.$street字段,但不能以这种方式更新rooms字段,使用$setField作为聚合管道的一部分来更新以$为前缀的顶级字段
{
   "_id": "E123",
   "address": {
      "$number": 123,
      "$street": "Elm Road"
   },
   "$rooms": {
      "br": 2,
      "bath": 1
   }
}
  1. 文档修改更新
# 当更新修改而不是替换现有文档字段时,$前缀字段可以使顶级字段名称,可以直接访问子字段,但是需要一个辅助方法来访问顶级字段。
{
   _id: ObjectId("610023ad7d58ecda39b8d161"),
   "part": "AB305",
   "$bin": 200,
   "quantity": 100,
   "pricing": { sale: true, "$discount": 60 }
}
# 可以直接查询pricing.$discount子字段
db.inventory.findAndModify({
	query: {'part': {$eq: 'AB305'}},
	update: {$inc: {'pricing.$discount': 10}}
})
# 使用$getField和$literal访问顶级$bin字段的值
db.inventory.findAndModify({
	query: {$expr: {
		$eq: [{$getField: {$literal: '$bin'}}, 200]
	}},
	update: {$inc: {'quantity': 10}}
})
  1. 使用聚合管道进行更新
# 在$replaceWith阶段使用$setField、$getField和$literal来修改聚合管道中以$为前缀的字段
{
   "_id": 100001,
   "$term": "fall",
   "registered": true,
   "grade": 4
}
# 使用管道为春季学期创建一个新集合
db.school.aggregate([
	{$match: {'registered': true}},
	{$replaceWith: {
		$setField: {
			field: {$literal: '$term'},
			input: '$$ROOT',
			value: 'spring'
		}
	}},
	{$out: 'spring2024'}
])
  1. 一般限制
以$为前缀的字段不能编入索引、用作分片密钥的一部分、使用$jsonSchema验证、使用转义序列进行修改、与字段级加密一起使用、用作_id文档中的子字段。

查询计划

  1. 对于任何给定的查询,在给定可用索引的情况下,mongodb查询规划器会选择并缓存最高效的查询计划。为了评估查询计划的效率,查询规划器会在试用期内运行所有候选计划。一般情况下,获胜计划是在试用期间产生最多结果同时执行最少工作量的查询计划。关联的计划缓存条目用于具有相同查询形状的后续查询。

在这里插入图片描述

  1. 规划缓存条目状态,从mongodb 4.2开始,每个查询结构都与缓存中的三种状态之一相关联。
缺失:缓存中不存在此形状的条目。对于查询,如果查询结构的缓存条目状态为Missing,对候选计划进行评估,并选出获选计划。缓存会为处于非活动状态的查询结构创建一个条目,该条目的值可量化计划所需的工作量。
非活动:缓存中的条目是此形状的占位符条目。规划器已查看形状,计算了一个值来量化计划所需的工作量,并存储了形状占位符条目,但查询结构不用于生成查询计划。对于查询,如果形状的缓存条目状态为Active,对候选计划进行评估,并选出候选计划。将量化计划所需工作量的所选计划的值与非活动条目的值进行比较。如果选定计划值为小于或等于非活动条目的值,所选计划取代了占位符非活动条目,并处于活动状态。大于非活动条目的值,非活动条目保持不变,但其量化计划所需工作量的值会递增。
活跃的:缓存中的条目是面向获胜计划的。规划器可以使用此条目生成查询计划。对于查询,如果查询结构的缓存条目状态为活动,活动条目用于生成查询计划。规划器还会评估条目的性能,如果其用于量化计划所需工作量的值不再符合选择条件,则会转换为非活动状态。
  1. 查询计划和缓存信息,要查看给定查询的查询计划信息,可以使用db.collection.explain()cursor.explain()。要查看集合的计划缓存信息,可以使用$planCacheStats聚合阶段。
  2. 计划缓存刷新,如果mongod重新启动或关闭,查询计划缓存将不复存在。此外,目录操作比如索引或集合删除会清除计划缓存。最近最少使用缓存机制会清除最近最少访问的缓存条目,而无论条目处于何种状态。此外还可以使用使用以下方法。
# 清除整个计划缓存
PlanCache.clear()
# 清除特定计划缓存条目
PlanCache.clearPlansByQuery()
  1. 计划缓存调试信息大小限制,从mongodb 5.0开始,当所有集合的计划缓存累计大小低于0.5GB时,计划缓存才会保存完整的计划缓存条目。当所有集合的计划缓存累计大小超过此阈值时,计划缓存会存储额外的计划缓存条目,但不会包含以下调试信息。计划缓存条目的估计大小以字节为单位可以在$planCacheStats的输出中找到。
createdFromQuery, cachedPlan, creationExecStats, candidatePlanScores
  1. 为了帮助识别具有相同查询结构的慢速查询,每个查询结构都与一个queryHash相关联。queryHash是一个十六进制字符串,表示查询结构的哈希值,并且仅依赖于查询结构。两个不同的查询结构可能会产生相同的哈希值,但是不同查询结构之间不太可能发生哈希冲突。

  2. 为了更深入地了解查询计划缓存,mongodb推出了planCacheKey,它是与查询关联的计划缓存条目键的哈希值,是查询结构和该结构当前可用索引的函数。如果添加或删除可以支持该查询结构的索引,则planCacheKey值可能会更改,而queryHash值不会更改。

  3. queryHashplanCacheKey用途

explain()输出字段queryPlanner.queryHash和queryPlanner.planCacheKey
记录慢速查询时的日志消息,包括分析器日志消息和诊断日志消息
$planCacheStats聚合阶段
PlanCache.listQueryShapes()方法/planCacheListQueryShapes命令
PlanCache.getPlansByQuery()方法/planCacheListPlans命令
  1. 索引筛选器
索引筛选器使用planCacheSetFilter命令设置,并确定优化器对查询结构评估哪些索引。查询结构由查询、排序和投影规范的组合组成。如果给定查询结构存在索引筛选器,则优化器仅考虑筛选器中指定的索引。
当查询结构存在索引筛选器时,mongodb会忽略hint()。要查看mongodb是否为查询结构应用了索引筛选器,检查db.collection.explain()方法的indexFilterSet字段。
索引筛选器仅影响优化器评估哪些索引,优化器仍可能选择collection扫描作为给定查询结构的获胜计划。索引筛选器在服务器进程期间存在,而在关闭后会消失。
诊断日志消息
$planCacheStats聚合阶段
PlanCache.listQueryShapes()方法/planCacheListQueryShapes命令
PlanCache.getPlansByQuery()方法/planCacheListPlans命令

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2199412.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

简历修订与求职经历 - Chap02.

最新的简历: 1.基本信息 姓名 ---- 学历 学位 本科 理学学士 专业 应用物理 智能仪器仪表 性别 男 出生年月 1976/7 电话 ---- 年龄 48 毕业时间 1998/6 电邮 ---- 籍贯 河南洛阳宜阳 居住地 河南郑州高新区 1.1 期望从事职业信息 机械仪器…

泡沫背后:人工智能的虚幻与现实

人工智能的盛世与泡沫 现今,人工智能热潮席卷科技行业,投资者、创业者和用户都被其光环吸引。然而,深入探讨这种现象,人工智能的泡沫正在形成,乃至具备崩溃的潜质。我们看到的,无非是一场由资本推动的狂欢…

双11大促最值得入手的好物是哪些?双11好物种草清单大全分享!

在这个全民狂欢的购物盛宴中,每个人都希望能以最优惠的价格买到心仪已久的好物,随着科技的飞速发展和生活品质的提升,市场上的产品琳琅满目,让人目不暇接,为了帮助大家在这个双11找到真正值得入手的好物,我…

chatGPT模型接口分享

前言: 仅供学习和交流,请合理使用。 API:https://api.gptnet.org key:sk-x9Rmq3HeHh5z9EIi8wFaXCl02OfxRSk5UAFodYm1o4zo5X3i 支持模型:gpt-3.5-turbo、gpt-3.5-turbo-16k、gpt-4o-mini、llama-3.1-405b 暂时支持以上四个模型…

一键生成PPT在线使用的保姆级教程:告别加班就靠它

已经过完24年所有的法定节假日的你,上班状态还好吗? 小编人倒是挺飘忽的,就那种人在工位,魂仍在青青大草原的感觉,都是牛马却失去了自由奔跑的权利...... 尤其是还要面对节前一堆没完成的工作,手动完成不…

基于Jenkins+K8S构建DevOps自动化运维管理平台

目录 1.k8s助力DevOps在企业落地实践 1.1 传统方式部署项目为什么发布慢,效率低? 1.2 上线一个功能,有多少时间被浪费了? 1.3 如何解决发布慢,效率低的问题呢? 1.5 什么是DevOps? 1.5.1 敏…

买电容笔需要注意什么?2024平替电容笔推荐清单,小白必看!

在当今数字化快速发展的时代,电容笔作为一种重要的数字书写和创作工具,正日益受到人们的青睐。然而现在市场上的电容笔品牌繁多,我们还是需要提前了解产品的情况,避免进入到商家的陷阱中。下面我会介绍电容笔的一些避坑知识&#…

关于常见数据库中SQL分页语法整理

标题 MySQL分页查询常规版升级版 Oracle分页查询常规版升级版 PostgreSQL分页查询常规版升级版 SQL Server分页查询常规版升级版 MySQL分页查询 在MySQL中,分页查询通常使用 LIMIT 关键字来实现。 LIMIT [offset,] rows 其中,offset表示偏移量&#xff…

LLMUnity:在Unity 3D中使用大模型

在本文中,我们将展示如何在 Unity 引擎中使用 LLM(大型语言模型)🎮。我们将使用 LLMUnity 包,并查看一些示例,了解如何仅用几行代码设置对话交互! 免责声明:我是 LLMUnity 的作者。…

STM32通用定时器TIM3的PWM输出实验配置步骤

通用定时器 PWM 输出实验 本小节我们来学习使用通用定时器的 PWM 输出模式。 脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。我们可以让定时…

Linux 命令 netstat 的 10 个基本用法

Netstat 简介 Netstat 是一款命令行工具,可用于列出系统上所有的网络套接字连接情况,包括 tcp, udp 以及 unix 套接字,另外它还能列出处于监听状态(即等待接入请求)的套接字。如果你想确认系统上的 Web 服务有没有起来…

(RAG)技术结合了大模型(LLM)与内部数据,从而实现更智能且相关性更高的 AI 应用

利用先进的生成式 AI 技术(如 RAG),释放数据的潜力,推动创新并获取战略优势 主要功能 使用向量数据库优化数据检索和生成 通过 AI 代理提升决策效率并自动化工作流程 克服实施真实 RAG 系统中的常见挑战 购买印刷版或 Kindle …

【C++】速通涉及 “vector” 的经典OJ编程题

【C】速通涉及 “vector” 的经典OJ编程题 一. 杨辉三角解题思路:代码实现: 二. 删除有序数组中的重复项解题思路:代码实现:【C/C】按位运算符使用规制 三. 只出现一次的数字解题思路:代码实现: 四. 只出现…

【D3.js in Action 3 精译_031】3.5.2 DIY实战:在 Observable 平台实现带数据标签的 D3 条形图并改造单元测试模块

当前内容所在位置(可进入专栏查看其他译好的章节内容) 第一部分 D3.js 基础知识 第一章 D3.js 简介(已完结) 1.1 何为 D3.js?1.2 D3 生态系统——入门须知1.3 数据可视化最佳实践(上)1.3 数据可…

PyQt5界面美化教程:一键切换四种风格

PyQt5界面美化教程:一键切换四种风格 关于作者 作者:小白熊 作者简介:精通python、matlab、c#语言,擅长机器学习,深度学习,机器视觉,目标检测,图像分类,姿态识别&#x…

【可答疑】基于51单片机的PWM控制智能台灯设计(含仿真、代码、报告、演示视频等)

✨哈喽大家好,这里是每天一杯冰美式oh,985电子本硕,大厂嵌入式在职0.3年,业余时间做做单片机小项目,有需要也可以提供就业指导(免费)~ 🐱‍🐉这是51单片机毕业设计100篇…

vue 同一个页面第二次跳转路由内容不更新

问题出现原因 在vue中相同路由之间跳转,默认在跳转路由时会采用缓存策略,并不会刷新当前路由组件。导致mounted(初始化),beforeDestory(销毁)等生命周期钩子函数并不会触发,从而产生路由跳转了,…

一文读懂Spring AOP的工作原理和机制(面试经)

导览 前言AOP(Aspect-Oriented Programming)必学必看1. 核心概念2. 主要原理3. 实践应用3.1 添加maven依赖3.2 定义切面Aspect3.3 定义Methods (join point) 切入点 结语精彩回顾 前言 在上文中,博主介绍了Spring IoC的核心原理和用法,相信你可以通过文…

Aria2Cloudreve任意文件写入到RCE

什么是Aria2 Aria2 是一个轻量级的命令行下载工具,支持多种下载协议,如 HTTP、FTP、SFTP、BitTorrent 和 Metalink。它以其强大的多源下载能力而著称,可以同时从多个服务器或对等节点下载文件,加快下载速度。Aria2 占用资源少&am…

UE4 材质学习笔记05(凹凸偏移和视差映射/纹理压缩设置)

一.凹凸偏移和视差映射 1.偏移映射 这需要一个高度图并且它的分辨率很低,只有256*256,事实上,如果高度图的分辨率比较低并且有点模糊,效果反而会更好 然后将高度图输出到BumpOffset节点的height插槽中, 之后利用得到…