{"kind": "plan", "major": "18", "item": {"slug": "memoize", "name": "Memoize", "name_zh": "Memoize", "category": "\u7269\u5316", "summary": "\u7f13\u5b58\u53c2\u6570\u5316\u5b50\u8ba1\u5212\u7684\u7ed3\u679c\uff0c\u5e76\u5728\u76f8\u540c\u53c2\u6570\u503c\u518d\u6b21\u51fa\u73b0\u65f6\u590d\u7528\u3002", "aliases": ["Memoize", "T_Memoize"], "content_hash": "518bd94b04a5a36c409c8dc92074579f3f713ace666a650e91323cc7a87d68d2", "versions": {"14": {"facts": [{"label": "\u6838\u5fc3\u8282\u70b9\u6807\u7b7e", "value": "T_Memoize"}, {"label": "\u7ed3\u6784\u5316 EXPLAIN \u8282\u70b9\u7c7b\u578b", "value": "Memoize"}, {"label": "\u8f93\u5165", "value": "\u4e00\u4e2a\u53c2\u6570\u5316\u5b50\u8ba1\u5212"}, {"label": "\u8f93\u51fa", "value": "\u5df2\u7f13\u5b58\u6216\u65b0\u4ea7\u751f\u7684\u5b50\u8ba1\u5212\u5143\u7ec4"}, {"label": "\u6267\u884c\u5668\u521d\u59cb\u5316\u51fd\u6570", "value": "ExecInitMemoize"}, {"label": "\u5185\u5b58\u673a\u5236", "value": "eviction"}], "memory": {"evidence": [{"url": "https://ftp.postgresql.org/pub/source/v14.24/postgresql-14.24.tar.bz2", "path": "src/backend/executor/nodeMemoize.c", "label": "src/backend/executor/nodeMemoize.c", "sha256": "4fa625c2308bc3e2124c1b0ed1e1401e38549d87f5413f3a8137ea3f0bb999d6", "archive_sha256": "a7fa7ed3d558172355f51406097a7bd4f6b473be80f311ef7cda96bf383d8897"}], "mechanism": "eviction", "description": "Memoize \u7f13\u5b58\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u6761\u76ee\uff0c\u4e0d\u5c06\u7f13\u5b58\u5143\u7ec4\u5199\u5165\u78c1\u76d8\u3002\u5982\u679c\u7ed3\u679c\u65e0\u6cd5\u653e\u5165\u7f13\u5b58\uff0c\u8be5\u6b21\u626b\u63cf\u53ef\u4ee5\u8fdb\u5165\u65c1\u8def\u6a21\u5f0f\u3002", "source_notes": ["The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.", "It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.", "cache_lookup Perform a lookup to see if we've already cached tuples based on the scan's current parameters. If we find an existing entry we move it to the end of the LRU list, set *found to true then return it. If we don't find an entry then we create a new one and add it to the end of the LRU list. We also update cache memory accounting and remove older entries if we go over the memory budget. If we managed to free enough memory we return the new entry, else we return NULL.", "If we've gone over our memory budget, then we'll free up some space in the cache."]}, "tables": [{"key": "explain-labels", "rows": [{"label": "Memoize", "identity": "Memoize"}], "title": "\u672c\u6784\u5efa\u4e2d\u7684 EXPLAIN \u6807\u7b7e", "columns": [{"key": "label", "label": "\u6587\u672c\u683c\u5f0f\u6807\u7b7e"}, {"key": "identity", "label": "\u7ed3\u6784\u5316\u8282\u70b9\u6807\u8bc6"}]}], "related": [{"url": "/wiki/sql/explain/?v=14", "label": "EXPLAIN"}, {"url": "/docs/14/using-explain.html", "label": "\u4f7f\u7528 EXPLAIN"}, {"url": "/docs/14/parallel-plans.html", "label": "\u5e76\u884c\u8ba1\u5212"}, {"url": "/wiki/guc/enable_memoize/?v=14", "label": "enable_memoize"}, {"url": "/wiki/guc/hash_mem_multiplier/?v=14", "label": "hash_mem_multiplier"}], "release": {"ref": "PostgreSQL 14.24 source archive", "label": "14.24", "major": "14", "channel": "stable", "revision": "a7fa7ed3d558172355f51406097a7bd4f6b473be80f311ef7cda96bf383d8897", "source_url": "https://ftp.postgresql.org/pub/source/v14.24/postgresql-14.24.tar.bz2", "source_snapshot_utc": ""}, "sources": [{"url": "https://ftp.postgresql.org/pub/source/v14.24/postgresql-14.24.tar.bz2", "line": 1309, "path": "src/backend/commands/explain.c", "label": "src/backend/commands/explain.c:1309", "sha256": "e091be4e2a083b8dea39ccd09beedede22c1716ef974da66c214a44f48be8c41", "archive_sha256": "a7fa7ed3d558172355f51406097a7bd4f6b473be80f311ef7cda96bf383d8897"}, {"url": "https://ftp.postgresql.org/pub/source/v14.24/postgresql-14.24.tar.bz2", "line": 330, "path": "src/backend/executor/execProcnode.c", "label": "src/backend/executor/execProcnode.c:330", "sha256": "72da1c5ad457f1d92a39ab73531701794df858419e3b89d6e6cb7079634e68fa", "archive_sha256": "a7fa7ed3d558172355f51406097a7bd4f6b473be80f311ef7cda96bf383d8897"}, {"url": "https://ftp.postgresql.org/pub/source/v14.24/postgresql-14.24.tar.bz2", "path": "src/backend/executor/nodeMemoize.c", "label": "src/backend/executor/nodeMemoize.c", "sha256": "4fa625c2308bc3e2124c1b0ed1e1401e38549d87f5413f3a8137ea3f0bb999d6", "archive_sha256": "a7fa7ed3d558172355f51406097a7bd4f6b473be80f311ef7cda96bf383d8897"}, {"url": "https://ftp.postgresql.org/pub/source/v14.24/postgresql-14.24.tar.bz2", "path": "src/include/nodes/plannodes.h", "label": "src/include/nodes/plannodes.h", "sha256": "302f51a16b570dba7ec4e7bc045f7df5800d21630280354d1a24025f3baec75d", "archive_sha256": "a7fa7ed3d558172355f51406097a7bd4f6b473be80f311ef7cda96bf383d8897"}], "node_tag": "T_Memoize", "sections": [{"title": "EXPLAIN \u540d\u79f0\u4e0e\u5c5e\u6027", "paragraphs": ["\u7ed3\u6784\u5316\u683c\u5f0f\u4f7f\u7528\u4e0a\u8ff0 Node Type\u3002\u6587\u672c\u683c\u5f0f\u540d\u79f0\u8fd8\u53ef\u80fd\u5305\u542b\u64cd\u4f5c\u3001\u7b56\u7565\u3001\u8fde\u63a5\u7c7b\u578b\u3001\u626b\u63cf\u65b9\u5411\u6216\u805a\u5408\u9636\u6bb5\u5c5e\u6027\u3002", "\u6b64\u6e90\u7801\u8bb0\u5f55\u7684\u6587\u672c\u540d\u79f0\uff1aMemoize.", "\u5e76\u884c\u611f\u77e5\u4e0e\u5e76\u884c\u5b89\u5168\u662f\u4e0d\u540c\u7684\u8ba1\u5212\u5c5e\u6027\u3002\u5728\u5e76\u884c\u5de5\u4f5c\u8fdb\u7a0b\u5185\u8fd0\u884c\u7684\u8282\u70b9\u4e0d\u4e00\u5b9a\u662f\u5e76\u884c\u611f\u77e5\u8282\u70b9\u3002"]}, {"title": "\u5185\u5b58\u4e0e\u4e34\u65f6\u5b58\u50a8", "paragraphs": ["Memoize \u7f13\u5b58\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u6761\u76ee\uff0c\u4e0d\u5c06\u7f13\u5b58\u5143\u7ec4\u5199\u5165\u78c1\u76d8\u3002\u5982\u679c\u7ed3\u679c\u65e0\u6cd5\u653e\u5165\u7f13\u5b58\uff0c\u8be5\u6b21\u626b\u63cf\u53ef\u4ee5\u8fdb\u5165\u65c1\u8def\u6a21\u5f0f\u3002", "\u7f13\u5b58\u91c7\u7528\u54c8\u5e0c\u8868\u3002\u7f13\u5b58\u7528\u6ee1\u65f6\u4e0d\u4f1a\u5c06\u5143\u7ec4\u5199\u5165\u78c1\u76d8\uff0c\u800c\u662f\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u7f13\u5b58\u6761\u76ee\u3002\u901a\u8fc7\u628a\u65b0\u6761\u76ee\u548c\u6bcf\u6b21\u67e5\u627e\u7684\u6761\u76ee\u79fb\u5230\u53cc\u5411\u94fe\u8868\u672b\u5c3e\u6765\u8bb0\u5f55\u4f7f\u7528\u6b21\u5e8f\uff0c\u4f7f\u8f83\u4e45\u672a\u7528\u7684\u6761\u76ee\u9010\u6e10\u79fb\u5230 LRU \u5217\u8868\u5934\u90e8\u3002", "\u4e3a\u4e00\u7ec4\u53c2\u6570\u586b\u5145\u7f13\u5b58\u65f6\uff0c\u53ef\u80fd\u65e0\u6cd5\u91ca\u653e\u8db3\u591f\u7684\u5185\u5b58\u6765\u5b58\u50a8\u66f4\u591a\u5143\u7ec4\u3002\u6b64\u65f6\u5176\u4ed6\u7f13\u5b58\u6761\u76ee\u5df2\u7ecf\u5168\u90e8\u88ab\u6dd8\u6c70\u3002\u5982\u679c\u518d\u7f13\u5b58\u4e00\u4e2a\u5143\u7ec4\u4f1a\u8d85\u51fa\u9884\u7b97\uff0c\u5fc5\u987b\u91ca\u653e\u6b63\u5728\u586b\u5145\u7684\u6761\u76ee\uff0c\u5e76\u5c06\u72b6\u6001\u673a\u5207\u6362\u4e3a MEMO_CACHE_BYPASS_MODE\u3002\u8fd9\u610f\u5473\u7740\u672c\u6b21\u626b\u63cf\u4e0d\u518d\u5c1d\u8bd5\u7f13\u5b58\u540e\u7eed\u5143\u7ec4\uff0c\u56e0\u4e3a\u5185\u5b58\u4e0d\u8db3\u3002\u4e0b\u6b21\u91cd\u65b0\u626b\u63cf\u65f6\u72b6\u6001\u673a\u4f1a\u91cd\u7f6e\uff1b\u5982\u679c\u4e0b\u4e00\u7ec4\u53c2\u6570\u7684\u5143\u7ec4\u7f13\u5b58\u9700\u8981\u8f83\u5c11\u5185\u5b58\uff0c\u5c31\u53ef\u80fd\u91cd\u65b0\u5f00\u59cb\u7f13\u5b58\u6709\u7528\u7684\u6761\u76ee\u3002", "cache_lookup \u6309\u626b\u63cf\u7684\u5f53\u524d\u53c2\u6570\u67e5\u627e\u662f\u5426\u5df2\u7f13\u5b58\u5143\u7ec4\u3002\u627e\u5230\u73b0\u6709\u6761\u76ee\u65f6\uff0c\u5c06\u5176\u79fb\u5230 LRU \u5217\u8868\u672b\u5c3e\uff0c\u5c06 *found \u8bbe\u4e3a true \u5e76\u8fd4\u56de\u3002\u627e\u4e0d\u5230\u65f6\uff0c\u65b0\u5efa\u6761\u76ee\u5e76\u52a0\u5165\u5217\u8868\u672b\u5c3e\uff0c\u540c\u65f6\u66f4\u65b0\u7f13\u5b58\u5185\u5b58\u8bb0\u8d26\uff1b\u5982\u679c\u8d85\u51fa\u9884\u7b97\u5219\u79fb\u9664\u8f83\u65e7\u6761\u76ee\u3002\u6210\u529f\u91ca\u653e\u8db3\u591f\u5185\u5b58\u65f6\u8fd4\u56de\u65b0\u6761\u76ee\uff0c\u5426\u5219\u8fd4\u56de NULL\u3002", "\u5982\u679c\u8d85\u51fa\u5185\u5b58\u9884\u7b97\uff0c\u5c31\u91ca\u653e\u90e8\u5206\u7f13\u5b58\u7a7a\u95f4\u3002"]}, {"title": "\u5e76\u884c\u6267\u884c\u4e0e\u8fd0\u884c\u4fe1\u606f\u91c7\u96c6", "paragraphs": ["\u4ee5\u4e0b\u6e90\u7801\u56de\u8c03\u53ef\u4ee5\u534f\u8c03\u6267\u884c\u6216\u6536\u96c6\u5de5\u4f5c\u8fdb\u7a0b\u7684\u6d4b\u91cf\u6570\u636e\u3002\u56de\u8c03\u5b58\u5728\u4e0d\u4ee3\u8868\u8be5\u8282\u70b9\u666e\u904d\u652f\u6301\u5171\u4eab\u5e76\u884c\u626b\u63cf\u6216\u5171\u4eab\u72b6\u6001\u3002", "\u6b64\u6784\u5efa\u7684\u56de\u8c03\uff1aExecMemoizeEstimate, ExecMemoizeInitializeDSM, ExecMemoizeInitializeWorker, ExecMemoizeRetrieveInstrumentation."]}, {"title": "\u6267\u884c\u5668\u5b9e\u73b0\u8bf4\u660e", "paragraphs": ["nodeMemoize.c\uff1a\u7f13\u5b58\u53c2\u6570\u5316\u8282\u70b9\u7ed3\u679c\u7684\u4f8b\u7a0b\u3002", "Memoize \u8282\u70b9\u4f4d\u4e8e\u8ba1\u5212\u6811\u4e2d\u7684\u53c2\u6570\u5316\u8282\u70b9\u4e4b\u4e0a\uff0c\u7528\u6765\u7f13\u5b58\u5176\u7ed3\u679c\u3002\u4f7f\u7528\u5df2\u7ecf\u51fa\u73b0\u8fc7\u7684\u53c2\u6570\u503c\u518d\u6b21\u626b\u63cf\u65f6\uff0c\u53ef\u4ee5\u76f4\u63a5\u4ece\u7f13\u5b58\u8bfb\u53d6\u5143\u7ec4\uff0c\u65e0\u9700\u91cd\u65b0\u626b\u63cf\u5185\u90e8\u8282\u70b9\u3002\u89c4\u5212\u5668\u8ba4\u4e3a\u91cd\u590d\u4f7f\u7528\u5df2\u6709\u53c2\u6570\u503c\u7684\u6982\u7387\u8db3\u4ee5\u62b5\u507f\u989d\u5916\u8282\u70b9\u5f00\u9500\u65f6\uff0c\u53ef\u80fd\u9009\u62e9\u8fd9\u79cd\u8282\u70b9\u3002", "\u7f13\u5b58\u91c7\u7528\u54c8\u5e0c\u8868\u3002\u7f13\u5b58\u7528\u6ee1\u65f6\u4e0d\u4f1a\u5c06\u5143\u7ec4\u5199\u5165\u78c1\u76d8\uff0c\u800c\u662f\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u7f13\u5b58\u6761\u76ee\u3002\u901a\u8fc7\u628a\u65b0\u6761\u76ee\u548c\u6bcf\u6b21\u67e5\u627e\u7684\u6761\u76ee\u79fb\u5230\u53cc\u5411\u94fe\u8868\u672b\u5c3e\u6765\u8bb0\u5f55\u4f7f\u7528\u6b21\u5e8f\uff0c\u4f7f\u8f83\u4e45\u672a\u7528\u7684\u6761\u76ee\u9010\u6e10\u79fb\u5230 LRU \u5217\u8868\u5934\u90e8\u3002", "\u8c03\u7528\u8005\u6709\u65f6\u4e0d\u4f1a\u5b8c\u6210\u5168\u90e8\u626b\u63cf\u3002\u4f8b\u5982\uff0c\u534a\u8fde\u63a5\u627e\u5230\u4e00\u4e2a\u5339\u914d\u5143\u7ec4\u540e\u5c31\u8df3\u5230\u4e0b\u4e00\u4e2a\u5916\u4fa7\u5143\u7ec4\uff0c\u4e0d\u518d\u7ee7\u7eed\u6b64\u6b21\u5185\u4fa7\u626b\u63cf\u3002\u56e0\u6b64\u5fc5\u987b\u8ddf\u8e2a\u7f13\u5b58\u6761\u76ee\u662f\u5426\u5b8c\u6574\uff1b\u901a\u5e38\u626b\u63cf\u8bfb\u4e0d\u5230\u66f4\u591a\u5143\u7ec4\u65f6\u624d\u53ef\u786e\u8ba4\u5b8c\u6574\u3002\u4e0d\u8fc7\u67d0\u4e9b\u60c5\u51b5\u4e0b\u4e0d\u5fc5\u8017\u5c3d\u626b\u63cf\u4fbf\u80fd\u6807\u8bb0\u5b8c\u6574\u3002\u4f8b\u5982\u552f\u4e00\u8fde\u63a5\u5df2\u77e5\u6bcf\u4e2a\u5916\u4fa7\u5143\u7ec4\u6700\u591a\u5339\u914d\u4e00\u884c\u3002\u4e3a\u652f\u6301\u8fd9\u79cd\u60c5\u51b5\uff0c\u7f13\u5b58\u5141\u8bb8\u8bbe\u7f6e singlerow \u9009\u9879\uff0c\u5728\u4ece\u5b50\u8282\u70b9\u8bfb\u53d6\u7b2c\u4e00\u4e2a\u5143\u7ec4\u540e\u5373\u6807\u8bb0\u8be5\u7f13\u5b58\u6761\u76ee\u5b8c\u6574\u3002", "\u4e3a\u4e00\u7ec4\u53c2\u6570\u586b\u5145\u7f13\u5b58\u65f6\uff0c\u53ef\u80fd\u65e0\u6cd5\u91ca\u653e\u8db3\u591f\u7684\u5185\u5b58\u6765\u5b58\u50a8\u66f4\u591a\u5143\u7ec4\u3002\u6b64\u65f6\u5176\u4ed6\u7f13\u5b58\u6761\u76ee\u5df2\u7ecf\u5168\u90e8\u88ab\u6dd8\u6c70\u3002\u5982\u679c\u518d\u7f13\u5b58\u4e00\u4e2a\u5143\u7ec4\u4f1a\u8d85\u51fa\u9884\u7b97\uff0c\u5fc5\u987b\u91ca\u653e\u6b63\u5728\u586b\u5145\u7684\u6761\u76ee\uff0c\u5e76\u5c06\u72b6\u6001\u673a\u5207\u6362\u4e3a MEMO_CACHE_BYPASS_MODE\u3002\u8fd9\u610f\u5473\u7740\u672c\u6b21\u626b\u63cf\u4e0d\u518d\u5c1d\u8bd5\u7f13\u5b58\u540e\u7eed\u5143\u7ec4\uff0c\u56e0\u4e3a\u5185\u5b58\u4e0d\u8db3\u3002\u4e0b\u6b21\u91cd\u65b0\u626b\u63cf\u65f6\u72b6\u6001\u673a\u4f1a\u91cd\u7f6e\uff1b\u5982\u679c\u4e0b\u4e00\u7ec4\u53c2\u6570\u7684\u5143\u7ec4\u7f13\u5b58\u9700\u8981\u8f83\u5c11\u5185\u5b58\uff0c\u5c31\u53ef\u80fd\u91cd\u65b0\u5f00\u59cb\u7f13\u5b58\u6709\u7528\u7684\u6761\u76ee\u3002"]}, {"code": "case T_Memoize:\n\t\t\tpname = sname = \"Memoize\";\n\t\t\tbreak;", "title": "\u6838\u5fc3\u6e90\u7801\u4e2d\u7684 EXPLAIN \u6807\u8bc6"}], "strategies": [], "description": ["\u7f13\u5b58\u53c2\u6570\u5316\u5b50\u8ba1\u5212\u7684\u7ed3\u679c\uff0c\u5e76\u5728\u76f8\u540c\u53c2\u6570\u503c\u518d\u6b21\u51fa\u73b0\u65f6\u590d\u7528\u3002"], "localization": {"status": "complete", "sources": [], "language": "zh", "original_text": {"/versions/14/description/0": "Caches results from a parameterized child and reuses them when the same parameter values recur.", "/versions/14/facts/0/label": "Core node tag", "/versions/14/facts/1/label": "Structured EXPLAIN Node Type", "/versions/14/facts/2/label": "Inputs", "/versions/14/facts/2/value": "One parameterized child plan", "/versions/14/facts/3/label": "Output", "/versions/14/facts/3/value": "Cached or newly produced child tuples", "/versions/14/facts/4/label": "Executor initializer", "/versions/14/facts/5/label": "Memory mechanism", "/versions/14/tables/0/title": "EXPLAIN labels in this source build", "/versions/14/related/1/label": "Using EXPLAIN", "/versions/14/related/2/label": "Parallel plans", "/versions/14/sections/0/title": "EXPLAIN names and attributes", "/versions/14/sections/1/title": "Memory and temporary storage", "/versions/14/sections/2/title": "Parallel execution and instrumentation", "/versions/14/sections/3/title": "Executor implementation notes", "/versions/14/sections/4/title": "EXPLAIN identity in core source", "/versions/14/memory/description": "The Memoize cache evicts least-recently-used entries instead of spilling cached tuples to disk. If a result cannot fit, caching can enter bypass mode for that scan.", "/versions/14/sections/0/paragraphs/0": "Structured formats use the Node Type above. Text-format spellings can also include operation, strategy, join type, scan direction or aggregation-stage attributes.", "/versions/14/sections/0/paragraphs/1": "Text names recorded by this source: Memoize.", "/versions/14/sections/0/paragraphs/2": "Parallel-aware and parallel-safe are different plan properties. A node running inside a parallel worker is not necessarily a parallel-aware node.", "/versions/14/sections/1/paragraphs/0": "The Memoize cache evicts least-recently-used entries instead of spilling cached tuples to disk. If a result cannot fit, caching can enter bypass mode for that scan.", "/versions/14/sections/1/paragraphs/1": "The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.", "/versions/14/sections/1/paragraphs/2": "It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.", "/versions/14/sections/1/paragraphs/3": "cache_lookup Perform a lookup to see if we've already cached tuples based on the scan's current parameters. If we find an existing entry we move it to the end of the LRU list, set *found to true then return it. If we don't find an entry then we create a new one and add it to the end of the LRU list. We also update cache memory accounting and remove older entries if we go over the memory budget. If we managed to free enough memory we return the new entry, else we return NULL.", "/versions/14/sections/1/paragraphs/4": "If we've gone over our memory budget, then we'll free up some space in the cache.", "/versions/14/sections/2/paragraphs/0": "The source callbacks below can coordinate execution or collect worker instrumentation. Their presence is not a blanket claim that this node supports a shared parallel scan or shared state.", "/versions/14/sections/2/paragraphs/1": "Callbacks in this build: ExecMemoizeEstimate, ExecMemoizeInitializeDSM, ExecMemoizeInitializeWorker, ExecMemoizeRetrieveInstrumentation.", "/versions/14/sections/3/paragraphs/0": "nodeMemoize.c Routines to handle caching of results from parameterized nodes", "/versions/14/sections/3/paragraphs/1": "Memoize nodes are intended to sit above parameterized nodes in the plan tree in order to cache results from them. The intention here is that a repeat scan with a parameter value that has already been seen by the node can fetch tuples from the cache rather than having to re-scan the inner node all over again. The query planner may choose to make use of one of these when it thinks rescans for previously seen values are likely enough to warrant adding the additional node.", "/versions/14/sections/3/paragraphs/2": "The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.", "/versions/14/sections/3/paragraphs/3": "Sometimes our callers won't run their scans to completion. For example a semi-join only needs to run until it finds a matching tuple, and once it does, the join operator skips to the next outer tuple and does not execute the inner side again on that scan. Because of this, we must keep track of when a cache entry is complete, and by default, we know it is when we run out of tuples to read during the scan. However, there are cases where we can mark the cache entry as complete without exhausting the scan of all tuples. One case is unique joins, where the join operator knows that there will only be at most one match for any given outer tuple. In order to support such cases we allow the \"singlerow\" option to be set for the cache. This option marks the cache entry as complete after we read the first tuple from the subnode.", "/versions/14/sections/3/paragraphs/4": "It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.", "/versions/14/tables/0/columns/0/label": "Text-format label", "/versions/14/tables/0/columns/1/label": "Structured node identity"}, "fallback_fields": [], "source_language": "en", "original_snapshot_sha256": "0713c4a4de878f3470530b01c5a521ad912af7ef3315eaa4211ecbc7f5f82b61"}, "evidence_kind": "source and documentation", "explain_names": ["Memoize"], "partial_modes": [], "comparison_data": {"node_tag": "T_Memoize", "strategies": [], "text_names": ["Memoize"], "initializer": "ExecInitMemoize", "partial_modes": [], "memory_mechanism": "eviction", "parallel_callbacks": ["ExecMemoizeEstimate", "ExecMemoizeInitializeDSM", "ExecMemoizeInitializeWorker", "ExecMemoizeRetrieveInstrumentation"]}, "comparison_hash": "ce79083064802c6772e741ce60e19e9c932b05d1f1a5ed00e8f8f2e86d678f5b", "explain_prefixes": ["Parallel", "Async"], "runtime_verified": false, "source_inventory": {"explain": "src/backend/commands/explain.c", "executor": "src/backend/executor/execProcnode.c", "implementation": "src/backend/executor/nodeMemoize.c"}, "parallel_callbacks": ["ExecMemoizeEstimate", "ExecMemoizeInitializeDSM", "ExecMemoizeInitializeWorker", "ExecMemoizeRetrieveInstrumentation"]}, "15": {"facts": [{"label": "\u6838\u5fc3\u8282\u70b9\u6807\u7b7e", "value": "T_Memoize"}, {"label": "\u7ed3\u6784\u5316 EXPLAIN \u8282\u70b9\u7c7b\u578b", "value": "Memoize"}, {"label": "\u8f93\u5165", "value": "\u4e00\u4e2a\u53c2\u6570\u5316\u5b50\u8ba1\u5212"}, {"label": "\u8f93\u51fa", "value": "\u5df2\u7f13\u5b58\u6216\u65b0\u4ea7\u751f\u7684\u5b50\u8ba1\u5212\u5143\u7ec4"}, {"label": "\u6267\u884c\u5668\u521d\u59cb\u5316\u51fd\u6570", "value": "ExecInitMemoize"}, {"label": "\u5185\u5b58\u673a\u5236", "value": "eviction"}], "memory": {"evidence": [{"url": "https://ftp.postgresql.org/pub/source/v15.19/postgresql-15.19.tar.bz2", "path": "src/backend/executor/nodeMemoize.c", "label": "src/backend/executor/nodeMemoize.c", "sha256": "6bdaef16826b37dfa1dffc57537267a99e45ef17828b566d000e3a235cfef612", "archive_sha256": "e1a64a87a46b825b88c082e4518161a47aab53c45694964f8ba1df28f7859f89"}], "mechanism": "eviction", "description": "Memoize \u7f13\u5b58\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u6761\u76ee\uff0c\u4e0d\u5c06\u7f13\u5b58\u5143\u7ec4\u5199\u5165\u78c1\u76d8\u3002\u5982\u679c\u7ed3\u679c\u65e0\u6cd5\u653e\u5165\u7f13\u5b58\uff0c\u8be5\u6b21\u626b\u63cf\u53ef\u4ee5\u8fdb\u5165\u65c1\u8def\u6a21\u5f0f\u3002", "source_notes": ["The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.", "It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.", "cache_lookup Perform a lookup to see if we've already cached tuples based on the scan's current parameters. If we find an existing entry we move it to the end of the LRU list, set *found to true then return it. If we don't find an entry then we create a new one and add it to the end of the LRU list. We also update cache memory accounting and remove older entries if we go over the memory budget. If we managed to free enough memory we return the new entry, else we return NULL.", "If we've gone over our memory budget, then we'll free up some space in the cache."]}, "tables": [{"key": "explain-labels", "rows": [{"label": "Memoize", "identity": "Memoize"}], "title": "\u672c\u6784\u5efa\u4e2d\u7684 EXPLAIN \u6807\u7b7e", "columns": [{"key": "label", "label": "\u6587\u672c\u683c\u5f0f\u6807\u7b7e"}, {"key": "identity", "label": "\u7ed3\u6784\u5316\u8282\u70b9\u6807\u8bc6"}]}], "related": [{"url": "/wiki/sql/explain/?v=15", "label": "EXPLAIN"}, {"url": "/docs/15/using-explain.html", "label": "\u4f7f\u7528 EXPLAIN"}, {"url": "/docs/15/parallel-plans.html", "label": "\u5e76\u884c\u8ba1\u5212"}, {"url": "/wiki/guc/enable_memoize/?v=15", "label": "enable_memoize"}, {"url": "/wiki/guc/hash_mem_multiplier/?v=15", "label": "hash_mem_multiplier"}], "release": {"ref": "PostgreSQL 15.19 source archive", "label": "15.19", "major": "15", "channel": "stable", "revision": "e1a64a87a46b825b88c082e4518161a47aab53c45694964f8ba1df28f7859f89", "source_url": "https://ftp.postgresql.org/pub/source/v15.19/postgresql-15.19.tar.bz2", "source_snapshot_utc": ""}, "sources": [{"url": "https://ftp.postgresql.org/pub/source/v15.19/postgresql-15.19.tar.bz2", "line": 1312, "path": "src/backend/commands/explain.c", "label": "src/backend/commands/explain.c:1312", "sha256": "bb3b442d0f1b098aa8707335250102f027a596cd94117308bd16d1d36b258f5c", "archive_sha256": "e1a64a87a46b825b88c082e4518161a47aab53c45694964f8ba1df28f7859f89"}, {"url": "https://ftp.postgresql.org/pub/source/v15.19/postgresql-15.19.tar.bz2", "line": 330, "path": "src/backend/executor/execProcnode.c", "label": "src/backend/executor/execProcnode.c:330", "sha256": "19836c50a272741a4eac653541e655437c2e00710a541e5348d6a277d0669d7c", "archive_sha256": "e1a64a87a46b825b88c082e4518161a47aab53c45694964f8ba1df28f7859f89"}, {"url": "https://ftp.postgresql.org/pub/source/v15.19/postgresql-15.19.tar.bz2", "path": "src/backend/executor/nodeMemoize.c", "label": "src/backend/executor/nodeMemoize.c", "sha256": "6bdaef16826b37dfa1dffc57537267a99e45ef17828b566d000e3a235cfef612", "archive_sha256": "e1a64a87a46b825b88c082e4518161a47aab53c45694964f8ba1df28f7859f89"}, {"url": "https://ftp.postgresql.org/pub/source/v15.19/postgresql-15.19.tar.bz2", "path": "src/include/nodes/plannodes.h", "label": "src/include/nodes/plannodes.h", "sha256": "fb4a4c8165495299131173680bc02a950d88e1ff610231fd97997bc0c9afc1d7", "archive_sha256": "e1a64a87a46b825b88c082e4518161a47aab53c45694964f8ba1df28f7859f89"}], "node_tag": "T_Memoize", "sections": [{"title": "EXPLAIN \u540d\u79f0\u4e0e\u5c5e\u6027", "paragraphs": ["\u7ed3\u6784\u5316\u683c\u5f0f\u4f7f\u7528\u4e0a\u8ff0 Node Type\u3002\u6587\u672c\u683c\u5f0f\u540d\u79f0\u8fd8\u53ef\u80fd\u5305\u542b\u64cd\u4f5c\u3001\u7b56\u7565\u3001\u8fde\u63a5\u7c7b\u578b\u3001\u626b\u63cf\u65b9\u5411\u6216\u805a\u5408\u9636\u6bb5\u5c5e\u6027\u3002", "\u6b64\u6e90\u7801\u8bb0\u5f55\u7684\u6587\u672c\u540d\u79f0\uff1aMemoize.", "\u5e76\u884c\u611f\u77e5\u4e0e\u5e76\u884c\u5b89\u5168\u662f\u4e0d\u540c\u7684\u8ba1\u5212\u5c5e\u6027\u3002\u5728\u5e76\u884c\u5de5\u4f5c\u8fdb\u7a0b\u5185\u8fd0\u884c\u7684\u8282\u70b9\u4e0d\u4e00\u5b9a\u662f\u5e76\u884c\u611f\u77e5\u8282\u70b9\u3002"]}, {"title": "\u5185\u5b58\u4e0e\u4e34\u65f6\u5b58\u50a8", "paragraphs": ["Memoize \u7f13\u5b58\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u6761\u76ee\uff0c\u4e0d\u5c06\u7f13\u5b58\u5143\u7ec4\u5199\u5165\u78c1\u76d8\u3002\u5982\u679c\u7ed3\u679c\u65e0\u6cd5\u653e\u5165\u7f13\u5b58\uff0c\u8be5\u6b21\u626b\u63cf\u53ef\u4ee5\u8fdb\u5165\u65c1\u8def\u6a21\u5f0f\u3002", "\u7f13\u5b58\u91c7\u7528\u54c8\u5e0c\u8868\u3002\u7f13\u5b58\u7528\u6ee1\u65f6\u4e0d\u4f1a\u5c06\u5143\u7ec4\u5199\u5165\u78c1\u76d8\uff0c\u800c\u662f\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u7f13\u5b58\u6761\u76ee\u3002\u901a\u8fc7\u628a\u65b0\u6761\u76ee\u548c\u6bcf\u6b21\u67e5\u627e\u7684\u6761\u76ee\u79fb\u5230\u53cc\u5411\u94fe\u8868\u672b\u5c3e\u6765\u8bb0\u5f55\u4f7f\u7528\u6b21\u5e8f\uff0c\u4f7f\u8f83\u4e45\u672a\u7528\u7684\u6761\u76ee\u9010\u6e10\u79fb\u5230 LRU \u5217\u8868\u5934\u90e8\u3002", "\u4e3a\u4e00\u7ec4\u53c2\u6570\u586b\u5145\u7f13\u5b58\u65f6\uff0c\u53ef\u80fd\u65e0\u6cd5\u91ca\u653e\u8db3\u591f\u7684\u5185\u5b58\u6765\u5b58\u50a8\u66f4\u591a\u5143\u7ec4\u3002\u6b64\u65f6\u5176\u4ed6\u7f13\u5b58\u6761\u76ee\u5df2\u7ecf\u5168\u90e8\u88ab\u6dd8\u6c70\u3002\u5982\u679c\u518d\u7f13\u5b58\u4e00\u4e2a\u5143\u7ec4\u4f1a\u8d85\u51fa\u9884\u7b97\uff0c\u5fc5\u987b\u91ca\u653e\u6b63\u5728\u586b\u5145\u7684\u6761\u76ee\uff0c\u5e76\u5c06\u72b6\u6001\u673a\u5207\u6362\u4e3a MEMO_CACHE_BYPASS_MODE\u3002\u8fd9\u610f\u5473\u7740\u672c\u6b21\u626b\u63cf\u4e0d\u518d\u5c1d\u8bd5\u7f13\u5b58\u540e\u7eed\u5143\u7ec4\uff0c\u56e0\u4e3a\u5185\u5b58\u4e0d\u8db3\u3002\u4e0b\u6b21\u91cd\u65b0\u626b\u63cf\u65f6\u72b6\u6001\u673a\u4f1a\u91cd\u7f6e\uff1b\u5982\u679c\u4e0b\u4e00\u7ec4\u53c2\u6570\u7684\u5143\u7ec4\u7f13\u5b58\u9700\u8981\u8f83\u5c11\u5185\u5b58\uff0c\u5c31\u53ef\u80fd\u91cd\u65b0\u5f00\u59cb\u7f13\u5b58\u6709\u7528\u7684\u6761\u76ee\u3002", "cache_lookup \u6309\u626b\u63cf\u7684\u5f53\u524d\u53c2\u6570\u67e5\u627e\u662f\u5426\u5df2\u7f13\u5b58\u5143\u7ec4\u3002\u627e\u5230\u73b0\u6709\u6761\u76ee\u65f6\uff0c\u5c06\u5176\u79fb\u5230 LRU \u5217\u8868\u672b\u5c3e\uff0c\u5c06 *found \u8bbe\u4e3a true \u5e76\u8fd4\u56de\u3002\u627e\u4e0d\u5230\u65f6\uff0c\u65b0\u5efa\u6761\u76ee\u5e76\u52a0\u5165\u5217\u8868\u672b\u5c3e\uff0c\u540c\u65f6\u66f4\u65b0\u7f13\u5b58\u5185\u5b58\u8bb0\u8d26\uff1b\u5982\u679c\u8d85\u51fa\u9884\u7b97\u5219\u79fb\u9664\u8f83\u65e7\u6761\u76ee\u3002\u6210\u529f\u91ca\u653e\u8db3\u591f\u5185\u5b58\u65f6\u8fd4\u56de\u65b0\u6761\u76ee\uff0c\u5426\u5219\u8fd4\u56de NULL\u3002", "\u5982\u679c\u8d85\u51fa\u5185\u5b58\u9884\u7b97\uff0c\u5c31\u91ca\u653e\u90e8\u5206\u7f13\u5b58\u7a7a\u95f4\u3002"]}, {"title": "\u5e76\u884c\u6267\u884c\u4e0e\u8fd0\u884c\u4fe1\u606f\u91c7\u96c6", "paragraphs": ["\u4ee5\u4e0b\u6e90\u7801\u56de\u8c03\u53ef\u4ee5\u534f\u8c03\u6267\u884c\u6216\u6536\u96c6\u5de5\u4f5c\u8fdb\u7a0b\u7684\u6d4b\u91cf\u6570\u636e\u3002\u56de\u8c03\u5b58\u5728\u4e0d\u4ee3\u8868\u8be5\u8282\u70b9\u666e\u904d\u652f\u6301\u5171\u4eab\u5e76\u884c\u626b\u63cf\u6216\u5171\u4eab\u72b6\u6001\u3002", "\u6b64\u6784\u5efa\u7684\u56de\u8c03\uff1aExecMemoizeEstimate, ExecMemoizeInitializeDSM, ExecMemoizeInitializeWorker, ExecMemoizeRetrieveInstrumentation."]}, {"title": "\u6267\u884c\u5668\u5b9e\u73b0\u8bf4\u660e", "paragraphs": ["nodeMemoize.c\uff1a\u7f13\u5b58\u53c2\u6570\u5316\u8282\u70b9\u7ed3\u679c\u7684\u4f8b\u7a0b\u3002", "Memoize \u8282\u70b9\u4f4d\u4e8e\u8ba1\u5212\u6811\u4e2d\u7684\u53c2\u6570\u5316\u8282\u70b9\u4e4b\u4e0a\uff0c\u7528\u6765\u7f13\u5b58\u5176\u7ed3\u679c\u3002\u4f7f\u7528\u5df2\u7ecf\u51fa\u73b0\u8fc7\u7684\u53c2\u6570\u503c\u518d\u6b21\u626b\u63cf\u65f6\uff0c\u53ef\u4ee5\u76f4\u63a5\u4ece\u7f13\u5b58\u8bfb\u53d6\u5143\u7ec4\uff0c\u65e0\u9700\u91cd\u65b0\u626b\u63cf\u5185\u90e8\u8282\u70b9\u3002\u89c4\u5212\u5668\u8ba4\u4e3a\u91cd\u590d\u4f7f\u7528\u5df2\u6709\u53c2\u6570\u503c\u7684\u6982\u7387\u8db3\u4ee5\u62b5\u507f\u989d\u5916\u8282\u70b9\u5f00\u9500\u65f6\uff0c\u53ef\u80fd\u9009\u62e9\u8fd9\u79cd\u8282\u70b9\u3002", "\u7f13\u5b58\u91c7\u7528\u54c8\u5e0c\u8868\u3002\u7f13\u5b58\u7528\u6ee1\u65f6\u4e0d\u4f1a\u5c06\u5143\u7ec4\u5199\u5165\u78c1\u76d8\uff0c\u800c\u662f\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u7f13\u5b58\u6761\u76ee\u3002\u901a\u8fc7\u628a\u65b0\u6761\u76ee\u548c\u6bcf\u6b21\u67e5\u627e\u7684\u6761\u76ee\u79fb\u5230\u53cc\u5411\u94fe\u8868\u672b\u5c3e\u6765\u8bb0\u5f55\u4f7f\u7528\u6b21\u5e8f\uff0c\u4f7f\u8f83\u4e45\u672a\u7528\u7684\u6761\u76ee\u9010\u6e10\u79fb\u5230 LRU \u5217\u8868\u5934\u90e8\u3002", "\u8c03\u7528\u8005\u6709\u65f6\u4e0d\u4f1a\u5b8c\u6210\u5168\u90e8\u626b\u63cf\u3002\u4f8b\u5982\uff0c\u534a\u8fde\u63a5\u627e\u5230\u4e00\u4e2a\u5339\u914d\u5143\u7ec4\u540e\u5c31\u8df3\u5230\u4e0b\u4e00\u4e2a\u5916\u4fa7\u5143\u7ec4\uff0c\u4e0d\u518d\u7ee7\u7eed\u6b64\u6b21\u5185\u4fa7\u626b\u63cf\u3002\u56e0\u6b64\u5fc5\u987b\u8ddf\u8e2a\u7f13\u5b58\u6761\u76ee\u662f\u5426\u5b8c\u6574\uff1b\u901a\u5e38\u626b\u63cf\u8bfb\u4e0d\u5230\u66f4\u591a\u5143\u7ec4\u65f6\u624d\u53ef\u786e\u8ba4\u5b8c\u6574\u3002\u4e0d\u8fc7\u67d0\u4e9b\u60c5\u51b5\u4e0b\u4e0d\u5fc5\u8017\u5c3d\u626b\u63cf\u4fbf\u80fd\u6807\u8bb0\u5b8c\u6574\u3002\u4f8b\u5982\u552f\u4e00\u8fde\u63a5\u5df2\u77e5\u6bcf\u4e2a\u5916\u4fa7\u5143\u7ec4\u6700\u591a\u5339\u914d\u4e00\u884c\u3002\u4e3a\u652f\u6301\u8fd9\u79cd\u60c5\u51b5\uff0c\u7f13\u5b58\u5141\u8bb8\u8bbe\u7f6e singlerow \u9009\u9879\uff0c\u5728\u4ece\u5b50\u8282\u70b9\u8bfb\u53d6\u7b2c\u4e00\u4e2a\u5143\u7ec4\u540e\u5373\u6807\u8bb0\u8be5\u7f13\u5b58\u6761\u76ee\u5b8c\u6574\u3002", "\u4e3a\u4e00\u7ec4\u53c2\u6570\u586b\u5145\u7f13\u5b58\u65f6\uff0c\u53ef\u80fd\u65e0\u6cd5\u91ca\u653e\u8db3\u591f\u7684\u5185\u5b58\u6765\u5b58\u50a8\u66f4\u591a\u5143\u7ec4\u3002\u6b64\u65f6\u5176\u4ed6\u7f13\u5b58\u6761\u76ee\u5df2\u7ecf\u5168\u90e8\u88ab\u6dd8\u6c70\u3002\u5982\u679c\u518d\u7f13\u5b58\u4e00\u4e2a\u5143\u7ec4\u4f1a\u8d85\u51fa\u9884\u7b97\uff0c\u5fc5\u987b\u91ca\u653e\u6b63\u5728\u586b\u5145\u7684\u6761\u76ee\uff0c\u5e76\u5c06\u72b6\u6001\u673a\u5207\u6362\u4e3a MEMO_CACHE_BYPASS_MODE\u3002\u8fd9\u610f\u5473\u7740\u672c\u6b21\u626b\u63cf\u4e0d\u518d\u5c1d\u8bd5\u7f13\u5b58\u540e\u7eed\u5143\u7ec4\uff0c\u56e0\u4e3a\u5185\u5b58\u4e0d\u8db3\u3002\u4e0b\u6b21\u91cd\u65b0\u626b\u63cf\u65f6\u72b6\u6001\u673a\u4f1a\u91cd\u7f6e\uff1b\u5982\u679c\u4e0b\u4e00\u7ec4\u53c2\u6570\u7684\u5143\u7ec4\u7f13\u5b58\u9700\u8981\u8f83\u5c11\u5185\u5b58\uff0c\u5c31\u53ef\u80fd\u91cd\u65b0\u5f00\u59cb\u7f13\u5b58\u6709\u7528\u7684\u6761\u76ee\u3002"]}, {"code": "case T_Memoize:\n\t\t\tpname = sname = \"Memoize\";\n\t\t\tbreak;", "title": "\u6838\u5fc3\u6e90\u7801\u4e2d\u7684 EXPLAIN \u6807\u8bc6"}], "strategies": [], "description": ["\u7f13\u5b58\u53c2\u6570\u5316\u5b50\u8ba1\u5212\u7684\u7ed3\u679c\uff0c\u5e76\u5728\u76f8\u540c\u53c2\u6570\u503c\u518d\u6b21\u51fa\u73b0\u65f6\u590d\u7528\u3002"], "localization": {"status": "complete", "sources": [], "language": "zh", "original_text": {"/versions/15/description/0": "Caches results from a parameterized child and reuses them when the same parameter values recur.", "/versions/15/facts/0/label": "Core node tag", "/versions/15/facts/1/label": "Structured EXPLAIN Node Type", "/versions/15/facts/2/label": "Inputs", "/versions/15/facts/2/value": "One parameterized child plan", "/versions/15/facts/3/label": "Output", "/versions/15/facts/3/value": "Cached or newly produced child tuples", "/versions/15/facts/4/label": "Executor initializer", "/versions/15/facts/5/label": "Memory mechanism", "/versions/15/tables/0/title": "EXPLAIN labels in this source build", "/versions/15/related/1/label": "Using EXPLAIN", "/versions/15/related/2/label": "Parallel plans", "/versions/15/sections/0/title": "EXPLAIN names and attributes", "/versions/15/sections/1/title": "Memory and temporary storage", "/versions/15/sections/2/title": "Parallel execution and instrumentation", "/versions/15/sections/3/title": "Executor implementation notes", "/versions/15/sections/4/title": "EXPLAIN identity in core source", "/versions/15/memory/description": "The Memoize cache evicts least-recently-used entries instead of spilling cached tuples to disk. If a result cannot fit, caching can enter bypass mode for that scan.", "/versions/15/sections/0/paragraphs/0": "Structured formats use the Node Type above. Text-format spellings can also include operation, strategy, join type, scan direction or aggregation-stage attributes.", "/versions/15/sections/0/paragraphs/1": "Text names recorded by this source: Memoize.", "/versions/15/sections/0/paragraphs/2": "Parallel-aware and parallel-safe are different plan properties. A node running inside a parallel worker is not necessarily a parallel-aware node.", "/versions/15/sections/1/paragraphs/0": "The Memoize cache evicts least-recently-used entries instead of spilling cached tuples to disk. If a result cannot fit, caching can enter bypass mode for that scan.", "/versions/15/sections/1/paragraphs/1": "The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.", "/versions/15/sections/1/paragraphs/2": "It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.", "/versions/15/sections/1/paragraphs/3": "cache_lookup Perform a lookup to see if we've already cached tuples based on the scan's current parameters. If we find an existing entry we move it to the end of the LRU list, set *found to true then return it. If we don't find an entry then we create a new one and add it to the end of the LRU list. We also update cache memory accounting and remove older entries if we go over the memory budget. If we managed to free enough memory we return the new entry, else we return NULL.", "/versions/15/sections/1/paragraphs/4": "If we've gone over our memory budget, then we'll free up some space in the cache.", "/versions/15/sections/2/paragraphs/0": "The source callbacks below can coordinate execution or collect worker instrumentation. Their presence is not a blanket claim that this node supports a shared parallel scan or shared state.", "/versions/15/sections/2/paragraphs/1": "Callbacks in this build: ExecMemoizeEstimate, ExecMemoizeInitializeDSM, ExecMemoizeInitializeWorker, ExecMemoizeRetrieveInstrumentation.", "/versions/15/sections/3/paragraphs/0": "nodeMemoize.c Routines to handle caching of results from parameterized nodes", "/versions/15/sections/3/paragraphs/1": "Memoize nodes are intended to sit above parameterized nodes in the plan tree in order to cache results from them. The intention here is that a repeat scan with a parameter value that has already been seen by the node can fetch tuples from the cache rather than having to re-scan the inner node all over again. The query planner may choose to make use of one of these when it thinks rescans for previously seen values are likely enough to warrant adding the additional node.", "/versions/15/sections/3/paragraphs/2": "The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.", "/versions/15/sections/3/paragraphs/3": "Sometimes our callers won't run their scans to completion. For example a semi-join only needs to run until it finds a matching tuple, and once it does, the join operator skips to the next outer tuple and does not execute the inner side again on that scan. Because of this, we must keep track of when a cache entry is complete, and by default, we know it is when we run out of tuples to read during the scan. However, there are cases where we can mark the cache entry as complete without exhausting the scan of all tuples. One case is unique joins, where the join operator knows that there will only be at most one match for any given outer tuple. In order to support such cases we allow the \"singlerow\" option to be set for the cache. This option marks the cache entry as complete after we read the first tuple from the subnode.", "/versions/15/sections/3/paragraphs/4": "It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.", "/versions/15/tables/0/columns/0/label": "Text-format label", "/versions/15/tables/0/columns/1/label": "Structured node identity"}, "fallback_fields": [], "source_language": "en", "original_snapshot_sha256": "a00082a6f1809ff1a6474e7ee96a4cf5acb6cd61906e7db6c33234fd9427a43e"}, "evidence_kind": "source and documentation", "explain_names": ["Memoize"], "partial_modes": [], "comparison_data": {"node_tag": "T_Memoize", "strategies": [], "text_names": ["Memoize"], "initializer": "ExecInitMemoize", "partial_modes": [], "memory_mechanism": "eviction", "parallel_callbacks": ["ExecMemoizeEstimate", "ExecMemoizeInitializeDSM", "ExecMemoizeInitializeWorker", "ExecMemoizeRetrieveInstrumentation"]}, "comparison_hash": "ce79083064802c6772e741ce60e19e9c932b05d1f1a5ed00e8f8f2e86d678f5b", "explain_prefixes": ["Parallel", "Async"], "runtime_verified": false, "source_inventory": {"explain": "src/backend/commands/explain.c", "executor": "src/backend/executor/execProcnode.c", "implementation": "src/backend/executor/nodeMemoize.c"}, "parallel_callbacks": ["ExecMemoizeEstimate", "ExecMemoizeInitializeDSM", "ExecMemoizeInitializeWorker", "ExecMemoizeRetrieveInstrumentation"]}, "16": {"facts": [{"label": "\u6838\u5fc3\u8282\u70b9\u6807\u7b7e", "value": "T_Memoize"}, {"label": "\u7ed3\u6784\u5316 EXPLAIN \u8282\u70b9\u7c7b\u578b", "value": "Memoize"}, {"label": "\u8f93\u5165", "value": "\u4e00\u4e2a\u53c2\u6570\u5316\u5b50\u8ba1\u5212"}, {"label": "\u8f93\u51fa", "value": "\u5df2\u7f13\u5b58\u6216\u65b0\u4ea7\u751f\u7684\u5b50\u8ba1\u5212\u5143\u7ec4"}, {"label": "\u6267\u884c\u5668\u521d\u59cb\u5316\u51fd\u6570", "value": "ExecInitMemoize"}, {"label": "\u5185\u5b58\u673a\u5236", "value": "eviction"}], "memory": {"evidence": [{"url": "https://ftp.postgresql.org/pub/source/v16.15/postgresql-16.15.tar.bz2", "path": "src/backend/executor/nodeMemoize.c", "label": "src/backend/executor/nodeMemoize.c", "sha256": "dcfe9d85b37288d38c271933437c0a4574a20afbe8748869814f9419a964679b", "archive_sha256": "c1575341fa7bd40f5274ea465b34390f4dc64cdd0770af327005caaeb9f6b7ed"}], "mechanism": "eviction", "description": "Memoize \u7f13\u5b58\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u6761\u76ee\uff0c\u4e0d\u5c06\u7f13\u5b58\u5143\u7ec4\u5199\u5165\u78c1\u76d8\u3002\u5982\u679c\u7ed3\u679c\u65e0\u6cd5\u653e\u5165\u7f13\u5b58\uff0c\u8be5\u6b21\u626b\u63cf\u53ef\u4ee5\u8fdb\u5165\u65c1\u8def\u6a21\u5f0f\u3002", "source_notes": ["The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.", "It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.", "cache_lookup Perform a lookup to see if we've already cached tuples based on the scan's current parameters. If we find an existing entry we move it to the end of the LRU list, set *found to true then return it. If we don't find an entry then we create a new one and add it to the end of the LRU list. We also update cache memory accounting and remove older entries if we go over the memory budget. If we managed to free enough memory we return the new entry, else we return NULL.", "If we've gone over our memory budget, then we'll free up some space in the cache."]}, "tables": [{"key": "explain-labels", "rows": [{"label": "Memoize", "identity": "Memoize"}], "title": "\u672c\u6784\u5efa\u4e2d\u7684 EXPLAIN \u6807\u7b7e", "columns": [{"key": "label", "label": "\u6587\u672c\u683c\u5f0f\u6807\u7b7e"}, {"key": "identity", "label": "\u7ed3\u6784\u5316\u8282\u70b9\u6807\u8bc6"}]}], "related": [{"url": "/wiki/sql/explain/?v=16", "label": "EXPLAIN"}, {"url": "/docs/16/using-explain.html", "label": "\u4f7f\u7528 EXPLAIN"}, {"url": "/docs/16/parallel-plans.html", "label": "\u5e76\u884c\u8ba1\u5212"}, {"url": "/wiki/guc/enable_memoize/?v=16", "label": "enable_memoize"}, {"url": "/wiki/guc/hash_mem_multiplier/?v=16", "label": "hash_mem_multiplier"}], "release": {"ref": "PostgreSQL 16.15 source archive", "label": "16.15", "major": "16", "channel": "stable", "revision": "c1575341fa7bd40f5274ea465b34390f4dc64cdd0770af327005caaeb9f6b7ed", "source_url": "https://ftp.postgresql.org/pub/source/v16.15/postgresql-16.15.tar.bz2", "source_snapshot_utc": ""}, "sources": [{"url": "https://ftp.postgresql.org/pub/source/v16.15/postgresql-16.15.tar.bz2", "line": 1345, "path": "src/backend/commands/explain.c", "label": "src/backend/commands/explain.c:1345", "sha256": "8e017f0116dbea471339b40c37a667cc9f95039e7e0329c783e5e8ce194de7e1", "archive_sha256": "c1575341fa7bd40f5274ea465b34390f4dc64cdd0770af327005caaeb9f6b7ed"}, {"url": "https://ftp.postgresql.org/pub/source/v16.15/postgresql-16.15.tar.bz2", "line": 330, "path": "src/backend/executor/execProcnode.c", "label": "src/backend/executor/execProcnode.c:330", "sha256": "e48c08e555f8cb4e4bb43df516c4b8906ce9bc374b2a745d98a1fc8c22cc5099", "archive_sha256": "c1575341fa7bd40f5274ea465b34390f4dc64cdd0770af327005caaeb9f6b7ed"}, {"url": "https://ftp.postgresql.org/pub/source/v16.15/postgresql-16.15.tar.bz2", "path": "src/backend/executor/nodeMemoize.c", "label": "src/backend/executor/nodeMemoize.c", "sha256": "dcfe9d85b37288d38c271933437c0a4574a20afbe8748869814f9419a964679b", "archive_sha256": "c1575341fa7bd40f5274ea465b34390f4dc64cdd0770af327005caaeb9f6b7ed"}, {"url": "https://ftp.postgresql.org/pub/source/v16.15/postgresql-16.15.tar.bz2", "path": "src/include/nodes/plannodes.h", "label": "src/include/nodes/plannodes.h", "sha256": "97db47353db76326b874589a5ad0a04501cc74cd72e237e7bd956e7472c41f1f", "archive_sha256": "c1575341fa7bd40f5274ea465b34390f4dc64cdd0770af327005caaeb9f6b7ed"}], "node_tag": "T_Memoize", "sections": [{"title": "EXPLAIN \u540d\u79f0\u4e0e\u5c5e\u6027", "paragraphs": ["\u7ed3\u6784\u5316\u683c\u5f0f\u4f7f\u7528\u4e0a\u8ff0 Node Type\u3002\u6587\u672c\u683c\u5f0f\u540d\u79f0\u8fd8\u53ef\u80fd\u5305\u542b\u64cd\u4f5c\u3001\u7b56\u7565\u3001\u8fde\u63a5\u7c7b\u578b\u3001\u626b\u63cf\u65b9\u5411\u6216\u805a\u5408\u9636\u6bb5\u5c5e\u6027\u3002", "\u6b64\u6e90\u7801\u8bb0\u5f55\u7684\u6587\u672c\u540d\u79f0\uff1aMemoize.", "\u5e76\u884c\u611f\u77e5\u4e0e\u5e76\u884c\u5b89\u5168\u662f\u4e0d\u540c\u7684\u8ba1\u5212\u5c5e\u6027\u3002\u5728\u5e76\u884c\u5de5\u4f5c\u8fdb\u7a0b\u5185\u8fd0\u884c\u7684\u8282\u70b9\u4e0d\u4e00\u5b9a\u662f\u5e76\u884c\u611f\u77e5\u8282\u70b9\u3002"]}, {"title": "\u5185\u5b58\u4e0e\u4e34\u65f6\u5b58\u50a8", "paragraphs": ["Memoize \u7f13\u5b58\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u6761\u76ee\uff0c\u4e0d\u5c06\u7f13\u5b58\u5143\u7ec4\u5199\u5165\u78c1\u76d8\u3002\u5982\u679c\u7ed3\u679c\u65e0\u6cd5\u653e\u5165\u7f13\u5b58\uff0c\u8be5\u6b21\u626b\u63cf\u53ef\u4ee5\u8fdb\u5165\u65c1\u8def\u6a21\u5f0f\u3002", "\u7f13\u5b58\u91c7\u7528\u54c8\u5e0c\u8868\u3002\u7f13\u5b58\u7528\u6ee1\u65f6\u4e0d\u4f1a\u5c06\u5143\u7ec4\u5199\u5165\u78c1\u76d8\uff0c\u800c\u662f\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u7f13\u5b58\u6761\u76ee\u3002\u901a\u8fc7\u628a\u65b0\u6761\u76ee\u548c\u6bcf\u6b21\u67e5\u627e\u7684\u6761\u76ee\u79fb\u5230\u53cc\u5411\u94fe\u8868\u672b\u5c3e\u6765\u8bb0\u5f55\u4f7f\u7528\u6b21\u5e8f\uff0c\u4f7f\u8f83\u4e45\u672a\u7528\u7684\u6761\u76ee\u9010\u6e10\u79fb\u5230 LRU \u5217\u8868\u5934\u90e8\u3002", "\u4e3a\u4e00\u7ec4\u53c2\u6570\u586b\u5145\u7f13\u5b58\u65f6\uff0c\u53ef\u80fd\u65e0\u6cd5\u91ca\u653e\u8db3\u591f\u7684\u5185\u5b58\u6765\u5b58\u50a8\u66f4\u591a\u5143\u7ec4\u3002\u6b64\u65f6\u5176\u4ed6\u7f13\u5b58\u6761\u76ee\u5df2\u7ecf\u5168\u90e8\u88ab\u6dd8\u6c70\u3002\u5982\u679c\u518d\u7f13\u5b58\u4e00\u4e2a\u5143\u7ec4\u4f1a\u8d85\u51fa\u9884\u7b97\uff0c\u5fc5\u987b\u91ca\u653e\u6b63\u5728\u586b\u5145\u7684\u6761\u76ee\uff0c\u5e76\u5c06\u72b6\u6001\u673a\u5207\u6362\u4e3a MEMO_CACHE_BYPASS_MODE\u3002\u8fd9\u610f\u5473\u7740\u672c\u6b21\u626b\u63cf\u4e0d\u518d\u5c1d\u8bd5\u7f13\u5b58\u540e\u7eed\u5143\u7ec4\uff0c\u56e0\u4e3a\u5185\u5b58\u4e0d\u8db3\u3002\u4e0b\u6b21\u91cd\u65b0\u626b\u63cf\u65f6\u72b6\u6001\u673a\u4f1a\u91cd\u7f6e\uff1b\u5982\u679c\u4e0b\u4e00\u7ec4\u53c2\u6570\u7684\u5143\u7ec4\u7f13\u5b58\u9700\u8981\u8f83\u5c11\u5185\u5b58\uff0c\u5c31\u53ef\u80fd\u91cd\u65b0\u5f00\u59cb\u7f13\u5b58\u6709\u7528\u7684\u6761\u76ee\u3002", "cache_lookup \u6309\u626b\u63cf\u7684\u5f53\u524d\u53c2\u6570\u67e5\u627e\u662f\u5426\u5df2\u7f13\u5b58\u5143\u7ec4\u3002\u627e\u5230\u73b0\u6709\u6761\u76ee\u65f6\uff0c\u5c06\u5176\u79fb\u5230 LRU \u5217\u8868\u672b\u5c3e\uff0c\u5c06 *found \u8bbe\u4e3a true \u5e76\u8fd4\u56de\u3002\u627e\u4e0d\u5230\u65f6\uff0c\u65b0\u5efa\u6761\u76ee\u5e76\u52a0\u5165\u5217\u8868\u672b\u5c3e\uff0c\u540c\u65f6\u66f4\u65b0\u7f13\u5b58\u5185\u5b58\u8bb0\u8d26\uff1b\u5982\u679c\u8d85\u51fa\u9884\u7b97\u5219\u79fb\u9664\u8f83\u65e7\u6761\u76ee\u3002\u6210\u529f\u91ca\u653e\u8db3\u591f\u5185\u5b58\u65f6\u8fd4\u56de\u65b0\u6761\u76ee\uff0c\u5426\u5219\u8fd4\u56de NULL\u3002", "\u5982\u679c\u8d85\u51fa\u5185\u5b58\u9884\u7b97\uff0c\u5c31\u91ca\u653e\u90e8\u5206\u7f13\u5b58\u7a7a\u95f4\u3002"]}, {"title": "\u5e76\u884c\u6267\u884c\u4e0e\u8fd0\u884c\u4fe1\u606f\u91c7\u96c6", "paragraphs": ["\u4ee5\u4e0b\u6e90\u7801\u56de\u8c03\u53ef\u4ee5\u534f\u8c03\u6267\u884c\u6216\u6536\u96c6\u5de5\u4f5c\u8fdb\u7a0b\u7684\u6d4b\u91cf\u6570\u636e\u3002\u56de\u8c03\u5b58\u5728\u4e0d\u4ee3\u8868\u8be5\u8282\u70b9\u666e\u904d\u652f\u6301\u5171\u4eab\u5e76\u884c\u626b\u63cf\u6216\u5171\u4eab\u72b6\u6001\u3002", "\u6b64\u6784\u5efa\u7684\u56de\u8c03\uff1aExecMemoizeEstimate, ExecMemoizeInitializeDSM, ExecMemoizeInitializeWorker, ExecMemoizeRetrieveInstrumentation."]}, {"title": "\u6267\u884c\u5668\u5b9e\u73b0\u8bf4\u660e", "paragraphs": ["nodeMemoize.c\uff1a\u7f13\u5b58\u53c2\u6570\u5316\u8282\u70b9\u7ed3\u679c\u7684\u4f8b\u7a0b\u3002", "Memoize \u8282\u70b9\u4f4d\u4e8e\u8ba1\u5212\u6811\u4e2d\u7684\u53c2\u6570\u5316\u8282\u70b9\u4e4b\u4e0a\uff0c\u7528\u6765\u7f13\u5b58\u5176\u7ed3\u679c\u3002\u4f7f\u7528\u5df2\u7ecf\u51fa\u73b0\u8fc7\u7684\u53c2\u6570\u503c\u518d\u6b21\u626b\u63cf\u65f6\uff0c\u53ef\u4ee5\u76f4\u63a5\u4ece\u7f13\u5b58\u8bfb\u53d6\u5143\u7ec4\uff0c\u65e0\u9700\u91cd\u65b0\u626b\u63cf\u5185\u90e8\u8282\u70b9\u3002\u89c4\u5212\u5668\u8ba4\u4e3a\u91cd\u590d\u4f7f\u7528\u5df2\u6709\u53c2\u6570\u503c\u7684\u6982\u7387\u8db3\u4ee5\u62b5\u507f\u989d\u5916\u8282\u70b9\u5f00\u9500\u65f6\uff0c\u53ef\u80fd\u9009\u62e9\u8fd9\u79cd\u8282\u70b9\u3002", "\u7f13\u5b58\u91c7\u7528\u54c8\u5e0c\u8868\u3002\u7f13\u5b58\u7528\u6ee1\u65f6\u4e0d\u4f1a\u5c06\u5143\u7ec4\u5199\u5165\u78c1\u76d8\uff0c\u800c\u662f\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u7f13\u5b58\u6761\u76ee\u3002\u901a\u8fc7\u628a\u65b0\u6761\u76ee\u548c\u6bcf\u6b21\u67e5\u627e\u7684\u6761\u76ee\u79fb\u5230\u53cc\u5411\u94fe\u8868\u672b\u5c3e\u6765\u8bb0\u5f55\u4f7f\u7528\u6b21\u5e8f\uff0c\u4f7f\u8f83\u4e45\u672a\u7528\u7684\u6761\u76ee\u9010\u6e10\u79fb\u5230 LRU \u5217\u8868\u5934\u90e8\u3002", "\u8c03\u7528\u8005\u6709\u65f6\u4e0d\u4f1a\u5b8c\u6210\u5168\u90e8\u626b\u63cf\u3002\u4f8b\u5982\uff0c\u534a\u8fde\u63a5\u627e\u5230\u4e00\u4e2a\u5339\u914d\u5143\u7ec4\u540e\u5c31\u8df3\u5230\u4e0b\u4e00\u4e2a\u5916\u4fa7\u5143\u7ec4\uff0c\u4e0d\u518d\u7ee7\u7eed\u6b64\u6b21\u5185\u4fa7\u626b\u63cf\u3002\u56e0\u6b64\u5fc5\u987b\u8ddf\u8e2a\u7f13\u5b58\u6761\u76ee\u662f\u5426\u5b8c\u6574\uff1b\u901a\u5e38\u626b\u63cf\u8bfb\u4e0d\u5230\u66f4\u591a\u5143\u7ec4\u65f6\u624d\u53ef\u786e\u8ba4\u5b8c\u6574\u3002\u4e0d\u8fc7\u67d0\u4e9b\u60c5\u51b5\u4e0b\u4e0d\u5fc5\u8017\u5c3d\u626b\u63cf\u4fbf\u80fd\u6807\u8bb0\u5b8c\u6574\u3002\u4f8b\u5982\u552f\u4e00\u8fde\u63a5\u5df2\u77e5\u6bcf\u4e2a\u5916\u4fa7\u5143\u7ec4\u6700\u591a\u5339\u914d\u4e00\u884c\u3002\u4e3a\u652f\u6301\u8fd9\u79cd\u60c5\u51b5\uff0c\u7f13\u5b58\u5141\u8bb8\u8bbe\u7f6e singlerow \u9009\u9879\uff0c\u5728\u4ece\u5b50\u8282\u70b9\u8bfb\u53d6\u7b2c\u4e00\u4e2a\u5143\u7ec4\u540e\u5373\u6807\u8bb0\u8be5\u7f13\u5b58\u6761\u76ee\u5b8c\u6574\u3002", "\u4e3a\u4e00\u7ec4\u53c2\u6570\u586b\u5145\u7f13\u5b58\u65f6\uff0c\u53ef\u80fd\u65e0\u6cd5\u91ca\u653e\u8db3\u591f\u7684\u5185\u5b58\u6765\u5b58\u50a8\u66f4\u591a\u5143\u7ec4\u3002\u6b64\u65f6\u5176\u4ed6\u7f13\u5b58\u6761\u76ee\u5df2\u7ecf\u5168\u90e8\u88ab\u6dd8\u6c70\u3002\u5982\u679c\u518d\u7f13\u5b58\u4e00\u4e2a\u5143\u7ec4\u4f1a\u8d85\u51fa\u9884\u7b97\uff0c\u5fc5\u987b\u91ca\u653e\u6b63\u5728\u586b\u5145\u7684\u6761\u76ee\uff0c\u5e76\u5c06\u72b6\u6001\u673a\u5207\u6362\u4e3a MEMO_CACHE_BYPASS_MODE\u3002\u8fd9\u610f\u5473\u7740\u672c\u6b21\u626b\u63cf\u4e0d\u518d\u5c1d\u8bd5\u7f13\u5b58\u540e\u7eed\u5143\u7ec4\uff0c\u56e0\u4e3a\u5185\u5b58\u4e0d\u8db3\u3002\u4e0b\u6b21\u91cd\u65b0\u626b\u63cf\u65f6\u72b6\u6001\u673a\u4f1a\u91cd\u7f6e\uff1b\u5982\u679c\u4e0b\u4e00\u7ec4\u53c2\u6570\u7684\u5143\u7ec4\u7f13\u5b58\u9700\u8981\u8f83\u5c11\u5185\u5b58\uff0c\u5c31\u53ef\u80fd\u91cd\u65b0\u5f00\u59cb\u7f13\u5b58\u6709\u7528\u7684\u6761\u76ee\u3002"]}, {"code": "case T_Memoize:\n\t\t\tpname = sname = \"Memoize\";\n\t\t\tbreak;", "title": "\u6838\u5fc3\u6e90\u7801\u4e2d\u7684 EXPLAIN \u6807\u8bc6"}], "strategies": [], "description": ["\u7f13\u5b58\u53c2\u6570\u5316\u5b50\u8ba1\u5212\u7684\u7ed3\u679c\uff0c\u5e76\u5728\u76f8\u540c\u53c2\u6570\u503c\u518d\u6b21\u51fa\u73b0\u65f6\u590d\u7528\u3002"], "localization": {"status": "complete", "sources": [], "language": "zh", "original_text": {"/versions/16/description/0": "Caches results from a parameterized child and reuses them when the same parameter values recur.", "/versions/16/facts/0/label": "Core node tag", "/versions/16/facts/1/label": "Structured EXPLAIN Node Type", "/versions/16/facts/2/label": "Inputs", "/versions/16/facts/2/value": "One parameterized child plan", "/versions/16/facts/3/label": "Output", "/versions/16/facts/3/value": "Cached or newly produced child tuples", "/versions/16/facts/4/label": "Executor initializer", "/versions/16/facts/5/label": "Memory mechanism", "/versions/16/tables/0/title": "EXPLAIN labels in this source build", "/versions/16/related/1/label": "Using EXPLAIN", "/versions/16/related/2/label": "Parallel plans", "/versions/16/sections/0/title": "EXPLAIN names and attributes", "/versions/16/sections/1/title": "Memory and temporary storage", "/versions/16/sections/2/title": "Parallel execution and instrumentation", "/versions/16/sections/3/title": "Executor implementation notes", "/versions/16/sections/4/title": "EXPLAIN identity in core source", "/versions/16/memory/description": "The Memoize cache evicts least-recently-used entries instead of spilling cached tuples to disk. If a result cannot fit, caching can enter bypass mode for that scan.", "/versions/16/sections/0/paragraphs/0": "Structured formats use the Node Type above. Text-format spellings can also include operation, strategy, join type, scan direction or aggregation-stage attributes.", "/versions/16/sections/0/paragraphs/1": "Text names recorded by this source: Memoize.", "/versions/16/sections/0/paragraphs/2": "Parallel-aware and parallel-safe are different plan properties. A node running inside a parallel worker is not necessarily a parallel-aware node.", "/versions/16/sections/1/paragraphs/0": "The Memoize cache evicts least-recently-used entries instead of spilling cached tuples to disk. If a result cannot fit, caching can enter bypass mode for that scan.", "/versions/16/sections/1/paragraphs/1": "The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.", "/versions/16/sections/1/paragraphs/2": "It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.", "/versions/16/sections/1/paragraphs/3": "cache_lookup Perform a lookup to see if we've already cached tuples based on the scan's current parameters. If we find an existing entry we move it to the end of the LRU list, set *found to true then return it. If we don't find an entry then we create a new one and add it to the end of the LRU list. We also update cache memory accounting and remove older entries if we go over the memory budget. If we managed to free enough memory we return the new entry, else we return NULL.", "/versions/16/sections/1/paragraphs/4": "If we've gone over our memory budget, then we'll free up some space in the cache.", "/versions/16/sections/2/paragraphs/0": "The source callbacks below can coordinate execution or collect worker instrumentation. Their presence is not a blanket claim that this node supports a shared parallel scan or shared state.", "/versions/16/sections/2/paragraphs/1": "Callbacks in this build: ExecMemoizeEstimate, ExecMemoizeInitializeDSM, ExecMemoizeInitializeWorker, ExecMemoizeRetrieveInstrumentation.", "/versions/16/sections/3/paragraphs/0": "nodeMemoize.c Routines to handle caching of results from parameterized nodes", "/versions/16/sections/3/paragraphs/1": "Memoize nodes are intended to sit above parameterized nodes in the plan tree in order to cache results from them. The intention here is that a repeat scan with a parameter value that has already been seen by the node can fetch tuples from the cache rather than having to re-scan the inner node all over again. The query planner may choose to make use of one of these when it thinks rescans for previously seen values are likely enough to warrant adding the additional node.", "/versions/16/sections/3/paragraphs/2": "The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.", "/versions/16/sections/3/paragraphs/3": "Sometimes our callers won't run their scans to completion. For example a semi-join only needs to run until it finds a matching tuple, and once it does, the join operator skips to the next outer tuple and does not execute the inner side again on that scan. Because of this, we must keep track of when a cache entry is complete, and by default, we know it is when we run out of tuples to read during the scan. However, there are cases where we can mark the cache entry as complete without exhausting the scan of all tuples. One case is unique joins, where the join operator knows that there will only be at most one match for any given outer tuple. In order to support such cases we allow the \"singlerow\" option to be set for the cache. This option marks the cache entry as complete after we read the first tuple from the subnode.", "/versions/16/sections/3/paragraphs/4": "It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.", "/versions/16/tables/0/columns/0/label": "Text-format label", "/versions/16/tables/0/columns/1/label": "Structured node identity"}, "fallback_fields": [], "source_language": "en", "original_snapshot_sha256": "ee09c9009724239106abab7fbd1dd8ee1de0e3c1233a090e1f3530b063c88d55"}, "evidence_kind": "source and documentation", "explain_names": ["Memoize"], "partial_modes": [], "comparison_data": {"node_tag": "T_Memoize", "strategies": [], "text_names": ["Memoize"], "initializer": "ExecInitMemoize", "partial_modes": [], "memory_mechanism": "eviction", "parallel_callbacks": ["ExecMemoizeEstimate", "ExecMemoizeInitializeDSM", "ExecMemoizeInitializeWorker", "ExecMemoizeRetrieveInstrumentation"]}, "comparison_hash": "ce79083064802c6772e741ce60e19e9c932b05d1f1a5ed00e8f8f2e86d678f5b", "explain_prefixes": ["Parallel", "Async"], "runtime_verified": false, "source_inventory": {"explain": "src/backend/commands/explain.c", "executor": "src/backend/executor/execProcnode.c", "implementation": "src/backend/executor/nodeMemoize.c"}, "parallel_callbacks": ["ExecMemoizeEstimate", "ExecMemoizeInitializeDSM", "ExecMemoizeInitializeWorker", "ExecMemoizeRetrieveInstrumentation"]}, "17": {"facts": [{"label": "\u6838\u5fc3\u8282\u70b9\u6807\u7b7e", "value": "T_Memoize"}, {"label": "\u7ed3\u6784\u5316 EXPLAIN \u8282\u70b9\u7c7b\u578b", "value": "Memoize"}, {"label": "\u8f93\u5165", "value": "\u4e00\u4e2a\u53c2\u6570\u5316\u5b50\u8ba1\u5212"}, {"label": "\u8f93\u51fa", "value": "\u5df2\u7f13\u5b58\u6216\u65b0\u4ea7\u751f\u7684\u5b50\u8ba1\u5212\u5143\u7ec4"}, {"label": "\u6267\u884c\u5668\u521d\u59cb\u5316\u51fd\u6570", "value": "ExecInitMemoize"}, {"label": "\u5185\u5b58\u673a\u5236", "value": "eviction"}], "memory": {"evidence": [{"url": "https://ftp.postgresql.org/pub/source/v17.11/postgresql-17.11.tar.bz2", "path": "src/backend/executor/nodeMemoize.c", "label": "src/backend/executor/nodeMemoize.c", "sha256": "09591408c1fef29e67a4a8a4aab85d85405b3eb79ae3e941f5dd32a8b804f992", "archive_sha256": "dd27f2b3c59e73ed14aa3324901242bf69a032a6347805f274e6260322d42979"}], "mechanism": "eviction", "description": "Memoize \u7f13\u5b58\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u6761\u76ee\uff0c\u4e0d\u5c06\u7f13\u5b58\u5143\u7ec4\u5199\u5165\u78c1\u76d8\u3002\u5982\u679c\u7ed3\u679c\u65e0\u6cd5\u653e\u5165\u7f13\u5b58\uff0c\u8be5\u6b21\u626b\u63cf\u53ef\u4ee5\u8fdb\u5165\u65c1\u8def\u6a21\u5f0f\u3002", "source_notes": ["The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.", "It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.", "cache_lookup Perform a lookup to see if we've already cached tuples based on the scan's current parameters. If we find an existing entry we move it to the end of the LRU list, set *found to true then return it. If we don't find an entry then we create a new one and add it to the end of the LRU list. We also update cache memory accounting and remove older entries if we go over the memory budget. If we managed to free enough memory we return the new entry, else we return NULL.", "If we've gone over our memory budget, then we'll free up some space in the cache."]}, "tables": [{"key": "explain-labels", "rows": [{"label": "Memoize", "identity": "Memoize"}], "title": "\u672c\u6784\u5efa\u4e2d\u7684 EXPLAIN \u6807\u7b7e", "columns": [{"key": "label", "label": "\u6587\u672c\u683c\u5f0f\u6807\u7b7e"}, {"key": "identity", "label": "\u7ed3\u6784\u5316\u8282\u70b9\u6807\u8bc6"}]}], "related": [{"url": "/wiki/sql/explain/?v=17", "label": "EXPLAIN"}, {"url": "/docs/17/using-explain.html", "label": "\u4f7f\u7528 EXPLAIN"}, {"url": "/docs/17/parallel-plans.html", "label": "\u5e76\u884c\u8ba1\u5212"}, {"url": "/wiki/guc/enable_memoize/?v=17", "label": "enable_memoize"}, {"url": "/wiki/guc/hash_mem_multiplier/?v=17", "label": "hash_mem_multiplier"}], "release": {"ref": "PostgreSQL 17.11 source archive", "label": "17.11", "major": "17", "channel": "stable", "revision": "dd27f2b3c59e73ed14aa3324901242bf69a032a6347805f274e6260322d42979", "source_url": "https://ftp.postgresql.org/pub/source/v17.11/postgresql-17.11.tar.bz2", "source_snapshot_utc": ""}, "sources": [{"url": "https://ftp.postgresql.org/pub/source/v17.11/postgresql-17.11.tar.bz2", "line": 1534, "path": "src/backend/commands/explain.c", "label": "src/backend/commands/explain.c:1534", "sha256": "741251b1a3b6d269a52a673d42eb63b02e13a5872db7b359b137086ab21b63c8", "archive_sha256": "dd27f2b3c59e73ed14aa3324901242bf69a032a6347805f274e6260322d42979"}, {"url": "https://ftp.postgresql.org/pub/source/v17.11/postgresql-17.11.tar.bz2", "line": 330, "path": "src/backend/executor/execProcnode.c", "label": "src/backend/executor/execProcnode.c:330", "sha256": "a77576e158b94cb01fa8c5174ba133004eabdd727660323f8afc66c8d2e757b8", "archive_sha256": "dd27f2b3c59e73ed14aa3324901242bf69a032a6347805f274e6260322d42979"}, {"url": "https://ftp.postgresql.org/pub/source/v17.11/postgresql-17.11.tar.bz2", "path": "src/backend/executor/nodeMemoize.c", "label": "src/backend/executor/nodeMemoize.c", "sha256": "09591408c1fef29e67a4a8a4aab85d85405b3eb79ae3e941f5dd32a8b804f992", "archive_sha256": "dd27f2b3c59e73ed14aa3324901242bf69a032a6347805f274e6260322d42979"}, {"url": "https://ftp.postgresql.org/pub/source/v17.11/postgresql-17.11.tar.bz2", "path": "src/include/nodes/plannodes.h", "label": "src/include/nodes/plannodes.h", "sha256": "d390dd69e2d3f5085beb42b33e46ff0676a2959b916a12b82118a7e545f8e562", "archive_sha256": "dd27f2b3c59e73ed14aa3324901242bf69a032a6347805f274e6260322d42979"}], "node_tag": "T_Memoize", "sections": [{"title": "EXPLAIN \u540d\u79f0\u4e0e\u5c5e\u6027", "paragraphs": ["\u7ed3\u6784\u5316\u683c\u5f0f\u4f7f\u7528\u4e0a\u8ff0 Node Type\u3002\u6587\u672c\u683c\u5f0f\u540d\u79f0\u8fd8\u53ef\u80fd\u5305\u542b\u64cd\u4f5c\u3001\u7b56\u7565\u3001\u8fde\u63a5\u7c7b\u578b\u3001\u626b\u63cf\u65b9\u5411\u6216\u805a\u5408\u9636\u6bb5\u5c5e\u6027\u3002", "\u6b64\u6e90\u7801\u8bb0\u5f55\u7684\u6587\u672c\u540d\u79f0\uff1aMemoize.", "\u5e76\u884c\u611f\u77e5\u4e0e\u5e76\u884c\u5b89\u5168\u662f\u4e0d\u540c\u7684\u8ba1\u5212\u5c5e\u6027\u3002\u5728\u5e76\u884c\u5de5\u4f5c\u8fdb\u7a0b\u5185\u8fd0\u884c\u7684\u8282\u70b9\u4e0d\u4e00\u5b9a\u662f\u5e76\u884c\u611f\u77e5\u8282\u70b9\u3002"]}, {"title": "\u5185\u5b58\u4e0e\u4e34\u65f6\u5b58\u50a8", "paragraphs": ["Memoize \u7f13\u5b58\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u6761\u76ee\uff0c\u4e0d\u5c06\u7f13\u5b58\u5143\u7ec4\u5199\u5165\u78c1\u76d8\u3002\u5982\u679c\u7ed3\u679c\u65e0\u6cd5\u653e\u5165\u7f13\u5b58\uff0c\u8be5\u6b21\u626b\u63cf\u53ef\u4ee5\u8fdb\u5165\u65c1\u8def\u6a21\u5f0f\u3002", "\u7f13\u5b58\u91c7\u7528\u54c8\u5e0c\u8868\u3002\u7f13\u5b58\u7528\u6ee1\u65f6\u4e0d\u4f1a\u5c06\u5143\u7ec4\u5199\u5165\u78c1\u76d8\uff0c\u800c\u662f\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u7f13\u5b58\u6761\u76ee\u3002\u901a\u8fc7\u628a\u65b0\u6761\u76ee\u548c\u6bcf\u6b21\u67e5\u627e\u7684\u6761\u76ee\u79fb\u5230\u53cc\u5411\u94fe\u8868\u672b\u5c3e\u6765\u8bb0\u5f55\u4f7f\u7528\u6b21\u5e8f\uff0c\u4f7f\u8f83\u4e45\u672a\u7528\u7684\u6761\u76ee\u9010\u6e10\u79fb\u5230 LRU \u5217\u8868\u5934\u90e8\u3002", "\u4e3a\u4e00\u7ec4\u53c2\u6570\u586b\u5145\u7f13\u5b58\u65f6\uff0c\u53ef\u80fd\u65e0\u6cd5\u91ca\u653e\u8db3\u591f\u7684\u5185\u5b58\u6765\u5b58\u50a8\u66f4\u591a\u5143\u7ec4\u3002\u6b64\u65f6\u5176\u4ed6\u7f13\u5b58\u6761\u76ee\u5df2\u7ecf\u5168\u90e8\u88ab\u6dd8\u6c70\u3002\u5982\u679c\u518d\u7f13\u5b58\u4e00\u4e2a\u5143\u7ec4\u4f1a\u8d85\u51fa\u9884\u7b97\uff0c\u5fc5\u987b\u91ca\u653e\u6b63\u5728\u586b\u5145\u7684\u6761\u76ee\uff0c\u5e76\u5c06\u72b6\u6001\u673a\u5207\u6362\u4e3a MEMO_CACHE_BYPASS_MODE\u3002\u8fd9\u610f\u5473\u7740\u672c\u6b21\u626b\u63cf\u4e0d\u518d\u5c1d\u8bd5\u7f13\u5b58\u540e\u7eed\u5143\u7ec4\uff0c\u56e0\u4e3a\u5185\u5b58\u4e0d\u8db3\u3002\u4e0b\u6b21\u91cd\u65b0\u626b\u63cf\u65f6\u72b6\u6001\u673a\u4f1a\u91cd\u7f6e\uff1b\u5982\u679c\u4e0b\u4e00\u7ec4\u53c2\u6570\u7684\u5143\u7ec4\u7f13\u5b58\u9700\u8981\u8f83\u5c11\u5185\u5b58\uff0c\u5c31\u53ef\u80fd\u91cd\u65b0\u5f00\u59cb\u7f13\u5b58\u6709\u7528\u7684\u6761\u76ee\u3002", "cache_lookup \u6309\u626b\u63cf\u7684\u5f53\u524d\u53c2\u6570\u67e5\u627e\u662f\u5426\u5df2\u7f13\u5b58\u5143\u7ec4\u3002\u627e\u5230\u73b0\u6709\u6761\u76ee\u65f6\uff0c\u5c06\u5176\u79fb\u5230 LRU \u5217\u8868\u672b\u5c3e\uff0c\u5c06 *found \u8bbe\u4e3a true \u5e76\u8fd4\u56de\u3002\u627e\u4e0d\u5230\u65f6\uff0c\u65b0\u5efa\u6761\u76ee\u5e76\u52a0\u5165\u5217\u8868\u672b\u5c3e\uff0c\u540c\u65f6\u66f4\u65b0\u7f13\u5b58\u5185\u5b58\u8bb0\u8d26\uff1b\u5982\u679c\u8d85\u51fa\u9884\u7b97\u5219\u79fb\u9664\u8f83\u65e7\u6761\u76ee\u3002\u6210\u529f\u91ca\u653e\u8db3\u591f\u5185\u5b58\u65f6\u8fd4\u56de\u65b0\u6761\u76ee\uff0c\u5426\u5219\u8fd4\u56de NULL\u3002", "\u5982\u679c\u8d85\u51fa\u5185\u5b58\u9884\u7b97\uff0c\u5c31\u91ca\u653e\u90e8\u5206\u7f13\u5b58\u7a7a\u95f4\u3002"]}, {"title": "\u5e76\u884c\u6267\u884c\u4e0e\u8fd0\u884c\u4fe1\u606f\u91c7\u96c6", "paragraphs": ["\u4ee5\u4e0b\u6e90\u7801\u56de\u8c03\u53ef\u4ee5\u534f\u8c03\u6267\u884c\u6216\u6536\u96c6\u5de5\u4f5c\u8fdb\u7a0b\u7684\u6d4b\u91cf\u6570\u636e\u3002\u56de\u8c03\u5b58\u5728\u4e0d\u4ee3\u8868\u8be5\u8282\u70b9\u666e\u904d\u652f\u6301\u5171\u4eab\u5e76\u884c\u626b\u63cf\u6216\u5171\u4eab\u72b6\u6001\u3002", "\u6b64\u6784\u5efa\u7684\u56de\u8c03\uff1aExecMemoizeEstimate, ExecMemoizeInitializeDSM, ExecMemoizeInitializeWorker, ExecMemoizeRetrieveInstrumentation."]}, {"title": "\u6267\u884c\u5668\u5b9e\u73b0\u8bf4\u660e", "paragraphs": ["nodeMemoize.c\uff1a\u7f13\u5b58\u53c2\u6570\u5316\u8282\u70b9\u7ed3\u679c\u7684\u4f8b\u7a0b\u3002", "Memoize \u8282\u70b9\u4f4d\u4e8e\u8ba1\u5212\u6811\u4e2d\u7684\u53c2\u6570\u5316\u8282\u70b9\u4e4b\u4e0a\uff0c\u7528\u6765\u7f13\u5b58\u5176\u7ed3\u679c\u3002\u4f7f\u7528\u5df2\u7ecf\u51fa\u73b0\u8fc7\u7684\u53c2\u6570\u503c\u518d\u6b21\u626b\u63cf\u65f6\uff0c\u53ef\u4ee5\u76f4\u63a5\u4ece\u7f13\u5b58\u8bfb\u53d6\u5143\u7ec4\uff0c\u65e0\u9700\u91cd\u65b0\u626b\u63cf\u5185\u90e8\u8282\u70b9\u3002\u89c4\u5212\u5668\u8ba4\u4e3a\u91cd\u590d\u4f7f\u7528\u5df2\u6709\u53c2\u6570\u503c\u7684\u6982\u7387\u8db3\u4ee5\u62b5\u507f\u989d\u5916\u8282\u70b9\u5f00\u9500\u65f6\uff0c\u53ef\u80fd\u9009\u62e9\u8fd9\u79cd\u8282\u70b9\u3002", "\u7f13\u5b58\u91c7\u7528\u54c8\u5e0c\u8868\u3002\u7f13\u5b58\u7528\u6ee1\u65f6\u4e0d\u4f1a\u5c06\u5143\u7ec4\u5199\u5165\u78c1\u76d8\uff0c\u800c\u662f\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u7f13\u5b58\u6761\u76ee\u3002\u901a\u8fc7\u628a\u65b0\u6761\u76ee\u548c\u6bcf\u6b21\u67e5\u627e\u7684\u6761\u76ee\u79fb\u5230\u53cc\u5411\u94fe\u8868\u672b\u5c3e\u6765\u8bb0\u5f55\u4f7f\u7528\u6b21\u5e8f\uff0c\u4f7f\u8f83\u4e45\u672a\u7528\u7684\u6761\u76ee\u9010\u6e10\u79fb\u5230 LRU \u5217\u8868\u5934\u90e8\u3002", "\u8c03\u7528\u8005\u6709\u65f6\u4e0d\u4f1a\u5b8c\u6210\u5168\u90e8\u626b\u63cf\u3002\u4f8b\u5982\uff0c\u534a\u8fde\u63a5\u627e\u5230\u4e00\u4e2a\u5339\u914d\u5143\u7ec4\u540e\u5c31\u8df3\u5230\u4e0b\u4e00\u4e2a\u5916\u4fa7\u5143\u7ec4\uff0c\u4e0d\u518d\u7ee7\u7eed\u6b64\u6b21\u5185\u4fa7\u626b\u63cf\u3002\u56e0\u6b64\u5fc5\u987b\u8ddf\u8e2a\u7f13\u5b58\u6761\u76ee\u662f\u5426\u5b8c\u6574\uff1b\u901a\u5e38\u626b\u63cf\u8bfb\u4e0d\u5230\u66f4\u591a\u5143\u7ec4\u65f6\u624d\u53ef\u786e\u8ba4\u5b8c\u6574\u3002\u4e0d\u8fc7\u67d0\u4e9b\u60c5\u51b5\u4e0b\u4e0d\u5fc5\u8017\u5c3d\u626b\u63cf\u4fbf\u80fd\u6807\u8bb0\u5b8c\u6574\u3002\u4f8b\u5982\u552f\u4e00\u8fde\u63a5\u5df2\u77e5\u6bcf\u4e2a\u5916\u4fa7\u5143\u7ec4\u6700\u591a\u5339\u914d\u4e00\u884c\u3002\u4e3a\u652f\u6301\u8fd9\u79cd\u60c5\u51b5\uff0c\u7f13\u5b58\u5141\u8bb8\u8bbe\u7f6e singlerow \u9009\u9879\uff0c\u5728\u4ece\u5b50\u8282\u70b9\u8bfb\u53d6\u7b2c\u4e00\u4e2a\u5143\u7ec4\u540e\u5373\u6807\u8bb0\u8be5\u7f13\u5b58\u6761\u76ee\u5b8c\u6574\u3002", "\u4e3a\u4e00\u7ec4\u53c2\u6570\u586b\u5145\u7f13\u5b58\u65f6\uff0c\u53ef\u80fd\u65e0\u6cd5\u91ca\u653e\u8db3\u591f\u7684\u5185\u5b58\u6765\u5b58\u50a8\u66f4\u591a\u5143\u7ec4\u3002\u6b64\u65f6\u5176\u4ed6\u7f13\u5b58\u6761\u76ee\u5df2\u7ecf\u5168\u90e8\u88ab\u6dd8\u6c70\u3002\u5982\u679c\u518d\u7f13\u5b58\u4e00\u4e2a\u5143\u7ec4\u4f1a\u8d85\u51fa\u9884\u7b97\uff0c\u5fc5\u987b\u91ca\u653e\u6b63\u5728\u586b\u5145\u7684\u6761\u76ee\uff0c\u5e76\u5c06\u72b6\u6001\u673a\u5207\u6362\u4e3a MEMO_CACHE_BYPASS_MODE\u3002\u8fd9\u610f\u5473\u7740\u672c\u6b21\u626b\u63cf\u4e0d\u518d\u5c1d\u8bd5\u7f13\u5b58\u540e\u7eed\u5143\u7ec4\uff0c\u56e0\u4e3a\u5185\u5b58\u4e0d\u8db3\u3002\u4e0b\u6b21\u91cd\u65b0\u626b\u63cf\u65f6\u72b6\u6001\u673a\u4f1a\u91cd\u7f6e\uff1b\u5982\u679c\u4e0b\u4e00\u7ec4\u53c2\u6570\u7684\u5143\u7ec4\u7f13\u5b58\u9700\u8981\u8f83\u5c11\u5185\u5b58\uff0c\u5c31\u53ef\u80fd\u91cd\u65b0\u5f00\u59cb\u7f13\u5b58\u6709\u7528\u7684\u6761\u76ee\u3002"]}, {"code": "case T_Memoize:\n\t\t\tpname = sname = \"Memoize\";\n\t\t\tbreak;", "title": "\u6838\u5fc3\u6e90\u7801\u4e2d\u7684 EXPLAIN \u6807\u8bc6"}], "strategies": [], "description": ["\u7f13\u5b58\u53c2\u6570\u5316\u5b50\u8ba1\u5212\u7684\u7ed3\u679c\uff0c\u5e76\u5728\u76f8\u540c\u53c2\u6570\u503c\u518d\u6b21\u51fa\u73b0\u65f6\u590d\u7528\u3002"], "localization": {"status": "complete", "sources": [], "language": "zh", "original_text": {"/versions/17/description/0": "Caches results from a parameterized child and reuses them when the same parameter values recur.", "/versions/17/facts/0/label": "Core node tag", "/versions/17/facts/1/label": "Structured EXPLAIN Node Type", "/versions/17/facts/2/label": "Inputs", "/versions/17/facts/2/value": "One parameterized child plan", "/versions/17/facts/3/label": "Output", "/versions/17/facts/3/value": "Cached or newly produced child tuples", "/versions/17/facts/4/label": "Executor initializer", "/versions/17/facts/5/label": "Memory mechanism", "/versions/17/tables/0/title": "EXPLAIN labels in this source build", "/versions/17/related/1/label": "Using EXPLAIN", "/versions/17/related/2/label": "Parallel plans", "/versions/17/sections/0/title": "EXPLAIN names and attributes", "/versions/17/sections/1/title": "Memory and temporary storage", "/versions/17/sections/2/title": "Parallel execution and instrumentation", "/versions/17/sections/3/title": "Executor implementation notes", "/versions/17/sections/4/title": "EXPLAIN identity in core source", "/versions/17/memory/description": "The Memoize cache evicts least-recently-used entries instead of spilling cached tuples to disk. If a result cannot fit, caching can enter bypass mode for that scan.", "/versions/17/sections/0/paragraphs/0": "Structured formats use the Node Type above. Text-format spellings can also include operation, strategy, join type, scan direction or aggregation-stage attributes.", "/versions/17/sections/0/paragraphs/1": "Text names recorded by this source: Memoize.", "/versions/17/sections/0/paragraphs/2": "Parallel-aware and parallel-safe are different plan properties. A node running inside a parallel worker is not necessarily a parallel-aware node.", "/versions/17/sections/1/paragraphs/0": "The Memoize cache evicts least-recently-used entries instead of spilling cached tuples to disk. If a result cannot fit, caching can enter bypass mode for that scan.", "/versions/17/sections/1/paragraphs/1": "The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.", "/versions/17/sections/1/paragraphs/2": "It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.", "/versions/17/sections/1/paragraphs/3": "cache_lookup Perform a lookup to see if we've already cached tuples based on the scan's current parameters. If we find an existing entry we move it to the end of the LRU list, set *found to true then return it. If we don't find an entry then we create a new one and add it to the end of the LRU list. We also update cache memory accounting and remove older entries if we go over the memory budget. If we managed to free enough memory we return the new entry, else we return NULL.", "/versions/17/sections/1/paragraphs/4": "If we've gone over our memory budget, then we'll free up some space in the cache.", "/versions/17/sections/2/paragraphs/0": "The source callbacks below can coordinate execution or collect worker instrumentation. Their presence is not a blanket claim that this node supports a shared parallel scan or shared state.", "/versions/17/sections/2/paragraphs/1": "Callbacks in this build: ExecMemoizeEstimate, ExecMemoizeInitializeDSM, ExecMemoizeInitializeWorker, ExecMemoizeRetrieveInstrumentation.", "/versions/17/sections/3/paragraphs/0": "nodeMemoize.c Routines to handle caching of results from parameterized nodes", "/versions/17/sections/3/paragraphs/1": "Memoize nodes are intended to sit above parameterized nodes in the plan tree in order to cache results from them. The intention here is that a repeat scan with a parameter value that has already been seen by the node can fetch tuples from the cache rather than having to re-scan the inner node all over again. The query planner may choose to make use of one of these when it thinks rescans for previously seen values are likely enough to warrant adding the additional node.", "/versions/17/sections/3/paragraphs/2": "The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.", "/versions/17/sections/3/paragraphs/3": "Sometimes our callers won't run their scans to completion. For example a semi-join only needs to run until it finds a matching tuple, and once it does, the join operator skips to the next outer tuple and does not execute the inner side again on that scan. Because of this, we must keep track of when a cache entry is complete, and by default, we know it is when we run out of tuples to read during the scan. However, there are cases where we can mark the cache entry as complete without exhausting the scan of all tuples. One case is unique joins, where the join operator knows that there will only be at most one match for any given outer tuple. In order to support such cases we allow the \"singlerow\" option to be set for the cache. This option marks the cache entry as complete after we read the first tuple from the subnode.", "/versions/17/sections/3/paragraphs/4": "It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.", "/versions/17/tables/0/columns/0/label": "Text-format label", "/versions/17/tables/0/columns/1/label": "Structured node identity"}, "fallback_fields": [], "source_language": "en", "original_snapshot_sha256": "8ae46eabd48f160100f3841fa5a72bbbc7076195b133f46686fabc34523e3f4b"}, "evidence_kind": "source and documentation", "explain_names": ["Memoize"], "partial_modes": [], "comparison_data": {"node_tag": "T_Memoize", "strategies": [], "text_names": ["Memoize"], "initializer": "ExecInitMemoize", "partial_modes": [], "memory_mechanism": "eviction", "parallel_callbacks": ["ExecMemoizeEstimate", "ExecMemoizeInitializeDSM", "ExecMemoizeInitializeWorker", "ExecMemoizeRetrieveInstrumentation"]}, "comparison_hash": "ce79083064802c6772e741ce60e19e9c932b05d1f1a5ed00e8f8f2e86d678f5b", "explain_prefixes": ["Parallel", "Async"], "runtime_verified": false, "source_inventory": {"explain": "src/backend/commands/explain.c", "executor": "src/backend/executor/execProcnode.c", "implementation": "src/backend/executor/nodeMemoize.c"}, "parallel_callbacks": ["ExecMemoizeEstimate", "ExecMemoizeInitializeDSM", "ExecMemoizeInitializeWorker", "ExecMemoizeRetrieveInstrumentation"]}, "18": {"facts": [{"label": "\u6838\u5fc3\u8282\u70b9\u6807\u7b7e", "value": "T_Memoize"}, {"label": "\u7ed3\u6784\u5316 EXPLAIN \u8282\u70b9\u7c7b\u578b", "value": "Memoize"}, {"label": "\u8f93\u5165", "value": "\u4e00\u4e2a\u53c2\u6570\u5316\u5b50\u8ba1\u5212"}, {"label": "\u8f93\u51fa", "value": "\u5df2\u7f13\u5b58\u6216\u65b0\u4ea7\u751f\u7684\u5b50\u8ba1\u5212\u5143\u7ec4"}, {"label": "\u6267\u884c\u5668\u521d\u59cb\u5316\u51fd\u6570", "value": "ExecInitMemoize"}, {"label": "\u5185\u5b58\u673a\u5236", "value": "eviction"}], "memory": {"evidence": [{"url": "https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2", "path": "src/backend/executor/nodeMemoize.c", "label": "src/backend/executor/nodeMemoize.c", "sha256": "1dec8adecc70763c97957ed94510c4734a399223fd2f81f5534a30351c06c5d6", "archive_sha256": "555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f"}], "mechanism": "eviction", "description": "Memoize \u7f13\u5b58\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u6761\u76ee\uff0c\u4e0d\u5c06\u7f13\u5b58\u5143\u7ec4\u5199\u5165\u78c1\u76d8\u3002\u5982\u679c\u7ed3\u679c\u65e0\u6cd5\u653e\u5165\u7f13\u5b58\uff0c\u8be5\u6b21\u626b\u63cf\u53ef\u4ee5\u8fdb\u5165\u65c1\u8def\u6a21\u5f0f\u3002", "source_notes": ["The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.", "It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.", "cache_lookup Perform a lookup to see if we've already cached tuples based on the scan's current parameters. If we find an existing entry we move it to the end of the LRU list, set *found to true then return it. If we don't find an entry then we create a new one and add it to the end of the LRU list. We also update cache memory accounting and remove older entries if we go over the memory budget. If we managed to free enough memory we return the new entry, else we return NULL.", "If we've gone over our memory budget, then we'll free up some space in the cache."]}, "tables": [{"key": "explain-labels", "rows": [{"label": "Memoize", "identity": "Memoize"}], "title": "\u672c\u6784\u5efa\u4e2d\u7684 EXPLAIN \u6807\u7b7e", "columns": [{"key": "label", "label": "\u6587\u672c\u683c\u5f0f\u6807\u7b7e"}, {"key": "identity", "label": "\u7ed3\u6784\u5316\u8282\u70b9\u6807\u8bc6"}]}], "related": [{"url": "/wiki/sql/explain/?v=18", "label": "EXPLAIN"}, {"url": "/docs/18/using-explain.html", "label": "\u4f7f\u7528 EXPLAIN"}, {"url": "/docs/18/parallel-plans.html", "label": "\u5e76\u884c\u8ba1\u5212"}, {"url": "/wiki/guc/enable_memoize/?v=18", "label": "enable_memoize"}, {"url": "/wiki/guc/hash_mem_multiplier/?v=18", "label": "hash_mem_multiplier"}], "release": {"ref": "PostgreSQL 18.6 source archive", "label": "18.6", "major": "18", "channel": "stable", "revision": "555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f", "source_url": "https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2", "source_snapshot_utc": ""}, "sources": [{"url": "https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2", "line": 1519, "path": "src/backend/commands/explain.c", "label": "src/backend/commands/explain.c:1519", "sha256": "34c86d6070224a0e981efef51f79101d6d505e5874f1684ace183034bab14bb4", "archive_sha256": "555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f"}, {"url": "https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2", "line": 330, "path": "src/backend/executor/execProcnode.c", "label": "src/backend/executor/execProcnode.c:330", "sha256": "f8a06a3f539077249b20664b2812433db6d7bd12b2c0ca633525db43d06f112a", "archive_sha256": "555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f"}, {"url": "https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2", "path": "src/backend/executor/nodeMemoize.c", "label": "src/backend/executor/nodeMemoize.c", "sha256": "1dec8adecc70763c97957ed94510c4734a399223fd2f81f5534a30351c06c5d6", "archive_sha256": "555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f"}, {"url": "https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2", "path": "src/include/nodes/plannodes.h", "label": "src/include/nodes/plannodes.h", "sha256": "52422b327a8049fbbb20d8b96008a0fc0a6fafa60f7eff3c695d5b2e83830120", "archive_sha256": "555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f"}], "node_tag": "T_Memoize", "sections": [{"title": "EXPLAIN \u540d\u79f0\u4e0e\u5c5e\u6027", "paragraphs": ["\u7ed3\u6784\u5316\u683c\u5f0f\u4f7f\u7528\u4e0a\u8ff0 Node Type\u3002\u6587\u672c\u683c\u5f0f\u540d\u79f0\u8fd8\u53ef\u80fd\u5305\u542b\u64cd\u4f5c\u3001\u7b56\u7565\u3001\u8fde\u63a5\u7c7b\u578b\u3001\u626b\u63cf\u65b9\u5411\u6216\u805a\u5408\u9636\u6bb5\u5c5e\u6027\u3002", "\u6b64\u6e90\u7801\u8bb0\u5f55\u7684\u6587\u672c\u540d\u79f0\uff1aMemoize.", "\u5e76\u884c\u611f\u77e5\u4e0e\u5e76\u884c\u5b89\u5168\u662f\u4e0d\u540c\u7684\u8ba1\u5212\u5c5e\u6027\u3002\u5728\u5e76\u884c\u5de5\u4f5c\u8fdb\u7a0b\u5185\u8fd0\u884c\u7684\u8282\u70b9\u4e0d\u4e00\u5b9a\u662f\u5e76\u884c\u611f\u77e5\u8282\u70b9\u3002"]}, {"title": "\u5185\u5b58\u4e0e\u4e34\u65f6\u5b58\u50a8", "paragraphs": ["Memoize \u7f13\u5b58\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u6761\u76ee\uff0c\u4e0d\u5c06\u7f13\u5b58\u5143\u7ec4\u5199\u5165\u78c1\u76d8\u3002\u5982\u679c\u7ed3\u679c\u65e0\u6cd5\u653e\u5165\u7f13\u5b58\uff0c\u8be5\u6b21\u626b\u63cf\u53ef\u4ee5\u8fdb\u5165\u65c1\u8def\u6a21\u5f0f\u3002", "\u7f13\u5b58\u91c7\u7528\u54c8\u5e0c\u8868\u3002\u7f13\u5b58\u7528\u6ee1\u65f6\u4e0d\u4f1a\u5c06\u5143\u7ec4\u5199\u5165\u78c1\u76d8\uff0c\u800c\u662f\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u7f13\u5b58\u6761\u76ee\u3002\u901a\u8fc7\u628a\u65b0\u6761\u76ee\u548c\u6bcf\u6b21\u67e5\u627e\u7684\u6761\u76ee\u79fb\u5230\u53cc\u5411\u94fe\u8868\u672b\u5c3e\u6765\u8bb0\u5f55\u4f7f\u7528\u6b21\u5e8f\uff0c\u4f7f\u8f83\u4e45\u672a\u7528\u7684\u6761\u76ee\u9010\u6e10\u79fb\u5230 LRU \u5217\u8868\u5934\u90e8\u3002", "\u4e3a\u4e00\u7ec4\u53c2\u6570\u586b\u5145\u7f13\u5b58\u65f6\uff0c\u53ef\u80fd\u65e0\u6cd5\u91ca\u653e\u8db3\u591f\u7684\u5185\u5b58\u6765\u5b58\u50a8\u66f4\u591a\u5143\u7ec4\u3002\u6b64\u65f6\u5176\u4ed6\u7f13\u5b58\u6761\u76ee\u5df2\u7ecf\u5168\u90e8\u88ab\u6dd8\u6c70\u3002\u5982\u679c\u518d\u7f13\u5b58\u4e00\u4e2a\u5143\u7ec4\u4f1a\u8d85\u51fa\u9884\u7b97\uff0c\u5fc5\u987b\u91ca\u653e\u6b63\u5728\u586b\u5145\u7684\u6761\u76ee\uff0c\u5e76\u5c06\u72b6\u6001\u673a\u5207\u6362\u4e3a MEMO_CACHE_BYPASS_MODE\u3002\u8fd9\u610f\u5473\u7740\u672c\u6b21\u626b\u63cf\u4e0d\u518d\u5c1d\u8bd5\u7f13\u5b58\u540e\u7eed\u5143\u7ec4\uff0c\u56e0\u4e3a\u5185\u5b58\u4e0d\u8db3\u3002\u4e0b\u6b21\u91cd\u65b0\u626b\u63cf\u65f6\u72b6\u6001\u673a\u4f1a\u91cd\u7f6e\uff1b\u5982\u679c\u4e0b\u4e00\u7ec4\u53c2\u6570\u7684\u5143\u7ec4\u7f13\u5b58\u9700\u8981\u8f83\u5c11\u5185\u5b58\uff0c\u5c31\u53ef\u80fd\u91cd\u65b0\u5f00\u59cb\u7f13\u5b58\u6709\u7528\u7684\u6761\u76ee\u3002", "cache_lookup \u6309\u626b\u63cf\u7684\u5f53\u524d\u53c2\u6570\u67e5\u627e\u662f\u5426\u5df2\u7f13\u5b58\u5143\u7ec4\u3002\u627e\u5230\u73b0\u6709\u6761\u76ee\u65f6\uff0c\u5c06\u5176\u79fb\u5230 LRU \u5217\u8868\u672b\u5c3e\uff0c\u5c06 *found \u8bbe\u4e3a true \u5e76\u8fd4\u56de\u3002\u627e\u4e0d\u5230\u65f6\uff0c\u65b0\u5efa\u6761\u76ee\u5e76\u52a0\u5165\u5217\u8868\u672b\u5c3e\uff0c\u540c\u65f6\u66f4\u65b0\u7f13\u5b58\u5185\u5b58\u8bb0\u8d26\uff1b\u5982\u679c\u8d85\u51fa\u9884\u7b97\u5219\u79fb\u9664\u8f83\u65e7\u6761\u76ee\u3002\u6210\u529f\u91ca\u653e\u8db3\u591f\u5185\u5b58\u65f6\u8fd4\u56de\u65b0\u6761\u76ee\uff0c\u5426\u5219\u8fd4\u56de NULL\u3002", "\u5982\u679c\u8d85\u51fa\u5185\u5b58\u9884\u7b97\uff0c\u5c31\u91ca\u653e\u90e8\u5206\u7f13\u5b58\u7a7a\u95f4\u3002"]}, {"title": "\u5e76\u884c\u6267\u884c\u4e0e\u8fd0\u884c\u4fe1\u606f\u91c7\u96c6", "paragraphs": ["\u4ee5\u4e0b\u6e90\u7801\u56de\u8c03\u53ef\u4ee5\u534f\u8c03\u6267\u884c\u6216\u6536\u96c6\u5de5\u4f5c\u8fdb\u7a0b\u7684\u6d4b\u91cf\u6570\u636e\u3002\u56de\u8c03\u5b58\u5728\u4e0d\u4ee3\u8868\u8be5\u8282\u70b9\u666e\u904d\u652f\u6301\u5171\u4eab\u5e76\u884c\u626b\u63cf\u6216\u5171\u4eab\u72b6\u6001\u3002", "\u6b64\u6784\u5efa\u7684\u56de\u8c03\uff1aExecMemoizeEstimate, ExecMemoizeInitializeDSM, ExecMemoizeInitializeWorker, ExecMemoizeRetrieveInstrumentation."]}, {"title": "\u6267\u884c\u5668\u5b9e\u73b0\u8bf4\u660e", "paragraphs": ["nodeMemoize.c\uff1a\u7f13\u5b58\u53c2\u6570\u5316\u8282\u70b9\u7ed3\u679c\u7684\u4f8b\u7a0b\u3002", "Memoize \u8282\u70b9\u4f4d\u4e8e\u8ba1\u5212\u6811\u4e2d\u7684\u53c2\u6570\u5316\u8282\u70b9\u4e4b\u4e0a\uff0c\u7528\u6765\u7f13\u5b58\u5176\u7ed3\u679c\u3002\u4f7f\u7528\u5df2\u7ecf\u51fa\u73b0\u8fc7\u7684\u53c2\u6570\u503c\u518d\u6b21\u626b\u63cf\u65f6\uff0c\u53ef\u4ee5\u76f4\u63a5\u4ece\u7f13\u5b58\u8bfb\u53d6\u5143\u7ec4\uff0c\u65e0\u9700\u91cd\u65b0\u626b\u63cf\u5185\u90e8\u8282\u70b9\u3002\u89c4\u5212\u5668\u8ba4\u4e3a\u91cd\u590d\u4f7f\u7528\u5df2\u6709\u53c2\u6570\u503c\u7684\u6982\u7387\u8db3\u4ee5\u62b5\u507f\u989d\u5916\u8282\u70b9\u5f00\u9500\u65f6\uff0c\u53ef\u80fd\u9009\u62e9\u8fd9\u79cd\u8282\u70b9\u3002", "\u7f13\u5b58\u91c7\u7528\u54c8\u5e0c\u8868\u3002\u7f13\u5b58\u7528\u6ee1\u65f6\u4e0d\u4f1a\u5c06\u5143\u7ec4\u5199\u5165\u78c1\u76d8\uff0c\u800c\u662f\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u7f13\u5b58\u6761\u76ee\u3002\u901a\u8fc7\u628a\u65b0\u6761\u76ee\u548c\u6bcf\u6b21\u67e5\u627e\u7684\u6761\u76ee\u79fb\u5230\u53cc\u5411\u94fe\u8868\u672b\u5c3e\u6765\u8bb0\u5f55\u4f7f\u7528\u6b21\u5e8f\uff0c\u4f7f\u8f83\u4e45\u672a\u7528\u7684\u6761\u76ee\u9010\u6e10\u79fb\u5230 LRU \u5217\u8868\u5934\u90e8\u3002", "\u8c03\u7528\u8005\u6709\u65f6\u4e0d\u4f1a\u5b8c\u6210\u5168\u90e8\u626b\u63cf\u3002\u4f8b\u5982\uff0c\u534a\u8fde\u63a5\u627e\u5230\u4e00\u4e2a\u5339\u914d\u5143\u7ec4\u540e\u5c31\u8df3\u5230\u4e0b\u4e00\u4e2a\u5916\u4fa7\u5143\u7ec4\uff0c\u4e0d\u518d\u7ee7\u7eed\u6b64\u6b21\u5185\u4fa7\u626b\u63cf\u3002\u56e0\u6b64\u5fc5\u987b\u8ddf\u8e2a\u7f13\u5b58\u6761\u76ee\u662f\u5426\u5b8c\u6574\uff1b\u901a\u5e38\u626b\u63cf\u8bfb\u4e0d\u5230\u66f4\u591a\u5143\u7ec4\u65f6\u624d\u53ef\u786e\u8ba4\u5b8c\u6574\u3002\u4e0d\u8fc7\u67d0\u4e9b\u60c5\u51b5\u4e0b\u4e0d\u5fc5\u8017\u5c3d\u626b\u63cf\u4fbf\u80fd\u6807\u8bb0\u5b8c\u6574\u3002\u4f8b\u5982\u552f\u4e00\u8fde\u63a5\u5df2\u77e5\u6bcf\u4e2a\u5916\u4fa7\u5143\u7ec4\u6700\u591a\u5339\u914d\u4e00\u884c\u3002\u4e3a\u652f\u6301\u8fd9\u79cd\u60c5\u51b5\uff0c\u7f13\u5b58\u5141\u8bb8\u8bbe\u7f6e singlerow \u9009\u9879\uff0c\u5728\u4ece\u5b50\u8282\u70b9\u8bfb\u53d6\u7b2c\u4e00\u4e2a\u5143\u7ec4\u540e\u5373\u6807\u8bb0\u8be5\u7f13\u5b58\u6761\u76ee\u5b8c\u6574\u3002", "\u4e3a\u4e00\u7ec4\u53c2\u6570\u586b\u5145\u7f13\u5b58\u65f6\uff0c\u53ef\u80fd\u65e0\u6cd5\u91ca\u653e\u8db3\u591f\u7684\u5185\u5b58\u6765\u5b58\u50a8\u66f4\u591a\u5143\u7ec4\u3002\u6b64\u65f6\u5176\u4ed6\u7f13\u5b58\u6761\u76ee\u5df2\u7ecf\u5168\u90e8\u88ab\u6dd8\u6c70\u3002\u5982\u679c\u518d\u7f13\u5b58\u4e00\u4e2a\u5143\u7ec4\u4f1a\u8d85\u51fa\u9884\u7b97\uff0c\u5fc5\u987b\u91ca\u653e\u6b63\u5728\u586b\u5145\u7684\u6761\u76ee\uff0c\u5e76\u5c06\u72b6\u6001\u673a\u5207\u6362\u4e3a MEMO_CACHE_BYPASS_MODE\u3002\u8fd9\u610f\u5473\u7740\u672c\u6b21\u626b\u63cf\u4e0d\u518d\u5c1d\u8bd5\u7f13\u5b58\u540e\u7eed\u5143\u7ec4\uff0c\u56e0\u4e3a\u5185\u5b58\u4e0d\u8db3\u3002\u4e0b\u6b21\u91cd\u65b0\u626b\u63cf\u65f6\u72b6\u6001\u673a\u4f1a\u91cd\u7f6e\uff1b\u5982\u679c\u4e0b\u4e00\u7ec4\u53c2\u6570\u7684\u5143\u7ec4\u7f13\u5b58\u9700\u8981\u8f83\u5c11\u5185\u5b58\uff0c\u5c31\u53ef\u80fd\u91cd\u65b0\u5f00\u59cb\u7f13\u5b58\u6709\u7528\u7684\u6761\u76ee\u3002"]}, {"code": "case T_Memoize:\n\t\t\tpname = sname = \"Memoize\";\n\t\t\tbreak;", "title": "\u6838\u5fc3\u6e90\u7801\u4e2d\u7684 EXPLAIN \u6807\u8bc6"}], "strategies": [], "description": ["\u7f13\u5b58\u53c2\u6570\u5316\u5b50\u8ba1\u5212\u7684\u7ed3\u679c\uff0c\u5e76\u5728\u76f8\u540c\u53c2\u6570\u503c\u518d\u6b21\u51fa\u73b0\u65f6\u590d\u7528\u3002"], "localization": {"status": "complete", "sources": [], "language": "zh", "original_text": {"/versions/18/description/0": "Caches results from a parameterized child and reuses them when the same parameter values recur.", "/versions/18/facts/0/label": "Core node tag", "/versions/18/facts/1/label": "Structured EXPLAIN Node Type", "/versions/18/facts/2/label": "Inputs", "/versions/18/facts/2/value": "One parameterized child plan", "/versions/18/facts/3/label": "Output", "/versions/18/facts/3/value": "Cached or newly produced child tuples", "/versions/18/facts/4/label": "Executor initializer", "/versions/18/facts/5/label": "Memory mechanism", "/versions/18/tables/0/title": "EXPLAIN labels in this source build", "/versions/18/related/1/label": "Using EXPLAIN", "/versions/18/related/2/label": "Parallel plans", "/versions/18/sections/0/title": "EXPLAIN names and attributes", "/versions/18/sections/1/title": "Memory and temporary storage", "/versions/18/sections/2/title": "Parallel execution and instrumentation", "/versions/18/sections/3/title": "Executor implementation notes", "/versions/18/sections/4/title": "EXPLAIN identity in core source", "/versions/18/memory/description": "The Memoize cache evicts least-recently-used entries instead of spilling cached tuples to disk. If a result cannot fit, caching can enter bypass mode for that scan.", "/versions/18/sections/0/paragraphs/0": "Structured formats use the Node Type above. Text-format spellings can also include operation, strategy, join type, scan direction or aggregation-stage attributes.", "/versions/18/sections/0/paragraphs/1": "Text names recorded by this source: Memoize.", "/versions/18/sections/0/paragraphs/2": "Parallel-aware and parallel-safe are different plan properties. A node running inside a parallel worker is not necessarily a parallel-aware node.", "/versions/18/sections/1/paragraphs/0": "The Memoize cache evicts least-recently-used entries instead of spilling cached tuples to disk. If a result cannot fit, caching can enter bypass mode for that scan.", "/versions/18/sections/1/paragraphs/1": "The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.", "/versions/18/sections/1/paragraphs/2": "It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.", "/versions/18/sections/1/paragraphs/3": "cache_lookup Perform a lookup to see if we've already cached tuples based on the scan's current parameters. If we find an existing entry we move it to the end of the LRU list, set *found to true then return it. If we don't find an entry then we create a new one and add it to the end of the LRU list. We also update cache memory accounting and remove older entries if we go over the memory budget. If we managed to free enough memory we return the new entry, else we return NULL.", "/versions/18/sections/1/paragraphs/4": "If we've gone over our memory budget, then we'll free up some space in the cache.", "/versions/18/sections/2/paragraphs/0": "The source callbacks below can coordinate execution or collect worker instrumentation. Their presence is not a blanket claim that this node supports a shared parallel scan or shared state.", "/versions/18/sections/2/paragraphs/1": "Callbacks in this build: ExecMemoizeEstimate, ExecMemoizeInitializeDSM, ExecMemoizeInitializeWorker, ExecMemoizeRetrieveInstrumentation.", "/versions/18/sections/3/paragraphs/0": "nodeMemoize.c Routines to handle caching of results from parameterized nodes", "/versions/18/sections/3/paragraphs/1": "Memoize nodes are intended to sit above parameterized nodes in the plan tree in order to cache results from them. The intention here is that a repeat scan with a parameter value that has already been seen by the node can fetch tuples from the cache rather than having to re-scan the inner node all over again. The query planner may choose to make use of one of these when it thinks rescans for previously seen values are likely enough to warrant adding the additional node.", "/versions/18/sections/3/paragraphs/2": "The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.", "/versions/18/sections/3/paragraphs/3": "Sometimes our callers won't run their scans to completion. For example a semi-join only needs to run until it finds a matching tuple, and once it does, the join operator skips to the next outer tuple and does not execute the inner side again on that scan. Because of this, we must keep track of when a cache entry is complete, and by default, we know it is when we run out of tuples to read during the scan. However, there are cases where we can mark the cache entry as complete without exhausting the scan of all tuples. One case is unique joins, where the join operator knows that there will only be at most one match for any given outer tuple. In order to support such cases we allow the \"singlerow\" option to be set for the cache. This option marks the cache entry as complete after we read the first tuple from the subnode.", "/versions/18/sections/3/paragraphs/4": "It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.", "/versions/18/tables/0/columns/0/label": "Text-format label", "/versions/18/tables/0/columns/1/label": "Structured node identity"}, "fallback_fields": [], "source_language": "en", "original_snapshot_sha256": "b731cc977a455651e09a53bbac518e5cb75d41abf449a3b6b6c7c0f4a6dc400e"}, "evidence_kind": "source and documentation", "explain_names": ["Memoize"], "partial_modes": [], "comparison_data": {"node_tag": "T_Memoize", "strategies": [], "text_names": ["Memoize"], "initializer": "ExecInitMemoize", "partial_modes": [], "memory_mechanism": "eviction", "parallel_callbacks": ["ExecMemoizeEstimate", "ExecMemoizeInitializeDSM", "ExecMemoizeInitializeWorker", "ExecMemoizeRetrieveInstrumentation"]}, "comparison_hash": "ce79083064802c6772e741ce60e19e9c932b05d1f1a5ed00e8f8f2e86d678f5b", "explain_prefixes": ["Parallel", "Async"], "runtime_verified": false, "source_inventory": {"explain": "src/backend/commands/explain.c", "executor": "src/backend/executor/execProcnode.c", "implementation": "src/backend/executor/nodeMemoize.c"}, "parallel_callbacks": ["ExecMemoizeEstimate", "ExecMemoizeInitializeDSM", "ExecMemoizeInitializeWorker", "ExecMemoizeRetrieveInstrumentation"]}, "19": {"facts": [{"label": "\u6838\u5fc3\u8282\u70b9\u6807\u7b7e", "value": "T_Memoize"}, {"label": "\u7ed3\u6784\u5316 EXPLAIN \u8282\u70b9\u7c7b\u578b", "value": "Memoize"}, {"label": "\u8f93\u5165", "value": "\u4e00\u4e2a\u53c2\u6570\u5316\u5b50\u8ba1\u5212"}, {"label": "\u8f93\u51fa", "value": "\u5df2\u7f13\u5b58\u6216\u65b0\u4ea7\u751f\u7684\u5b50\u8ba1\u5212\u5143\u7ec4"}, {"label": "\u6267\u884c\u5668\u521d\u59cb\u5316\u51fd\u6570", "value": "ExecInitMemoize"}, {"label": "\u5185\u5b58\u673a\u5236", "value": "eviction"}], "memory": {"evidence": [{"url": "https://ftp.postgresql.org/pub/source/v19beta4/postgresql-19beta4.tar.bz2", "path": "src/backend/executor/nodeMemoize.c", "label": "src/backend/executor/nodeMemoize.c", "sha256": "52e94b532ab2c703dc1232d6daa5268edd2e763054a9f6a34d0e7571d08ba7f8", "archive_sha256": "83157ee9c599d03b2f7a3d73ef3a56ec24e0e79cc2b3501a64d1364f56398c86"}], "mechanism": "eviction", "description": "Memoize \u7f13\u5b58\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u6761\u76ee\uff0c\u4e0d\u5c06\u7f13\u5b58\u5143\u7ec4\u5199\u5165\u78c1\u76d8\u3002\u5982\u679c\u7ed3\u679c\u65e0\u6cd5\u653e\u5165\u7f13\u5b58\uff0c\u8be5\u6b21\u626b\u63cf\u53ef\u4ee5\u8fdb\u5165\u65c1\u8def\u6a21\u5f0f\u3002", "source_notes": ["The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.", "It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.", "cache_lookup Perform a lookup to see if we've already cached tuples based on the scan's current parameters. If we find an existing entry we move it to the end of the LRU list, set *found to true then return it. If we don't find an entry then we create a new one and add it to the end of the LRU list. We also update cache memory accounting and remove older entries if we go over the memory budget. If we managed to free enough memory we return the new entry, else we return NULL.", "If we've gone over our memory budget, then we'll free up some space in the cache."]}, "tables": [{"key": "explain-labels", "rows": [{"label": "Memoize", "identity": "Memoize"}], "title": "\u672c\u6784\u5efa\u4e2d\u7684 EXPLAIN \u6807\u7b7e", "columns": [{"key": "label", "label": "\u6587\u672c\u683c\u5f0f\u6807\u7b7e"}, {"key": "identity", "label": "\u7ed3\u6784\u5316\u8282\u70b9\u6807\u8bc6"}]}], "related": [{"url": "/wiki/sql/explain/?v=19", "label": "EXPLAIN"}, {"url": "/docs/19/using-explain.html", "label": "\u4f7f\u7528 EXPLAIN"}, {"url": "/docs/19/parallel-plans.html", "label": "\u5e76\u884c\u8ba1\u5212"}, {"url": "/wiki/guc/enable_memoize/?v=19", "label": "enable_memoize"}, {"url": "/wiki/guc/hash_mem_multiplier/?v=19", "label": "hash_mem_multiplier"}], "release": {"ref": "PostgreSQL 19beta4 source archive", "label": "19beta4", "major": "19", "channel": "preview", "revision": "83157ee9c599d03b2f7a3d73ef3a56ec24e0e79cc2b3501a64d1364f56398c86", "source_url": "https://ftp.postgresql.org/pub/source/v19beta4/postgresql-19beta4.tar.bz2", "source_snapshot_utc": ""}, "sources": [{"url": "https://ftp.postgresql.org/pub/source/v19beta4/postgresql-19beta4.tar.bz2", "line": 1531, "path": "src/backend/commands/explain.c", "label": "src/backend/commands/explain.c:1531", "sha256": "8b115b1c194a4b54ae630209a741e293b1df49a9052f10b2de9ca092a48998e3", "archive_sha256": "83157ee9c599d03b2f7a3d73ef3a56ec24e0e79cc2b3501a64d1364f56398c86"}, {"url": "https://ftp.postgresql.org/pub/source/v19beta4/postgresql-19beta4.tar.bz2", "line": 330, "path": "src/backend/executor/execProcnode.c", "label": "src/backend/executor/execProcnode.c:330", "sha256": "5e39b2037bed672da55104229ecc32da5abde44c26bcad01479edcfa044d09ed", "archive_sha256": "83157ee9c599d03b2f7a3d73ef3a56ec24e0e79cc2b3501a64d1364f56398c86"}, {"url": "https://ftp.postgresql.org/pub/source/v19beta4/postgresql-19beta4.tar.bz2", "path": "src/backend/executor/nodeMemoize.c", "label": "src/backend/executor/nodeMemoize.c", "sha256": "52e94b532ab2c703dc1232d6daa5268edd2e763054a9f6a34d0e7571d08ba7f8", "archive_sha256": "83157ee9c599d03b2f7a3d73ef3a56ec24e0e79cc2b3501a64d1364f56398c86"}, {"url": "https://ftp.postgresql.org/pub/source/v19beta4/postgresql-19beta4.tar.bz2", "path": "src/include/nodes/plannodes.h", "label": "src/include/nodes/plannodes.h", "sha256": "1c65d5d6b6c81c71531685843647869bcae630779d815a5036b06e070c6c06c7", "archive_sha256": "83157ee9c599d03b2f7a3d73ef3a56ec24e0e79cc2b3501a64d1364f56398c86"}], "node_tag": "T_Memoize", "sections": [{"title": "EXPLAIN \u540d\u79f0\u4e0e\u5c5e\u6027", "paragraphs": ["\u7ed3\u6784\u5316\u683c\u5f0f\u4f7f\u7528\u4e0a\u8ff0 Node Type\u3002\u6587\u672c\u683c\u5f0f\u540d\u79f0\u8fd8\u53ef\u80fd\u5305\u542b\u64cd\u4f5c\u3001\u7b56\u7565\u3001\u8fde\u63a5\u7c7b\u578b\u3001\u626b\u63cf\u65b9\u5411\u6216\u805a\u5408\u9636\u6bb5\u5c5e\u6027\u3002", "\u6b64\u6e90\u7801\u8bb0\u5f55\u7684\u6587\u672c\u540d\u79f0\uff1aMemoize.", "\u5e76\u884c\u611f\u77e5\u4e0e\u5e76\u884c\u5b89\u5168\u662f\u4e0d\u540c\u7684\u8ba1\u5212\u5c5e\u6027\u3002\u5728\u5e76\u884c\u5de5\u4f5c\u8fdb\u7a0b\u5185\u8fd0\u884c\u7684\u8282\u70b9\u4e0d\u4e00\u5b9a\u662f\u5e76\u884c\u611f\u77e5\u8282\u70b9\u3002"]}, {"title": "\u5185\u5b58\u4e0e\u4e34\u65f6\u5b58\u50a8", "paragraphs": ["Memoize \u7f13\u5b58\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u6761\u76ee\uff0c\u4e0d\u5c06\u7f13\u5b58\u5143\u7ec4\u5199\u5165\u78c1\u76d8\u3002\u5982\u679c\u7ed3\u679c\u65e0\u6cd5\u653e\u5165\u7f13\u5b58\uff0c\u8be5\u6b21\u626b\u63cf\u53ef\u4ee5\u8fdb\u5165\u65c1\u8def\u6a21\u5f0f\u3002", "\u7f13\u5b58\u91c7\u7528\u54c8\u5e0c\u8868\u3002\u7f13\u5b58\u7528\u6ee1\u65f6\u4e0d\u4f1a\u5c06\u5143\u7ec4\u5199\u5165\u78c1\u76d8\uff0c\u800c\u662f\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u7f13\u5b58\u6761\u76ee\u3002\u901a\u8fc7\u628a\u65b0\u6761\u76ee\u548c\u6bcf\u6b21\u67e5\u627e\u7684\u6761\u76ee\u79fb\u5230\u53cc\u5411\u94fe\u8868\u672b\u5c3e\u6765\u8bb0\u5f55\u4f7f\u7528\u6b21\u5e8f\uff0c\u4f7f\u8f83\u4e45\u672a\u7528\u7684\u6761\u76ee\u9010\u6e10\u79fb\u5230 LRU \u5217\u8868\u5934\u90e8\u3002", "\u4e3a\u4e00\u7ec4\u53c2\u6570\u586b\u5145\u7f13\u5b58\u65f6\uff0c\u53ef\u80fd\u65e0\u6cd5\u91ca\u653e\u8db3\u591f\u7684\u5185\u5b58\u6765\u5b58\u50a8\u66f4\u591a\u5143\u7ec4\u3002\u6b64\u65f6\u5176\u4ed6\u7f13\u5b58\u6761\u76ee\u5df2\u7ecf\u5168\u90e8\u88ab\u6dd8\u6c70\u3002\u5982\u679c\u518d\u7f13\u5b58\u4e00\u4e2a\u5143\u7ec4\u4f1a\u8d85\u51fa\u9884\u7b97\uff0c\u5fc5\u987b\u91ca\u653e\u6b63\u5728\u586b\u5145\u7684\u6761\u76ee\uff0c\u5e76\u5c06\u72b6\u6001\u673a\u5207\u6362\u4e3a MEMO_CACHE_BYPASS_MODE\u3002\u8fd9\u610f\u5473\u7740\u672c\u6b21\u626b\u63cf\u4e0d\u518d\u5c1d\u8bd5\u7f13\u5b58\u540e\u7eed\u5143\u7ec4\uff0c\u56e0\u4e3a\u5185\u5b58\u4e0d\u8db3\u3002\u4e0b\u6b21\u91cd\u65b0\u626b\u63cf\u65f6\u72b6\u6001\u673a\u4f1a\u91cd\u7f6e\uff1b\u5982\u679c\u4e0b\u4e00\u7ec4\u53c2\u6570\u7684\u5143\u7ec4\u7f13\u5b58\u9700\u8981\u8f83\u5c11\u5185\u5b58\uff0c\u5c31\u53ef\u80fd\u91cd\u65b0\u5f00\u59cb\u7f13\u5b58\u6709\u7528\u7684\u6761\u76ee\u3002", "cache_lookup \u6309\u626b\u63cf\u7684\u5f53\u524d\u53c2\u6570\u67e5\u627e\u662f\u5426\u5df2\u7f13\u5b58\u5143\u7ec4\u3002\u627e\u5230\u73b0\u6709\u6761\u76ee\u65f6\uff0c\u5c06\u5176\u79fb\u5230 LRU \u5217\u8868\u672b\u5c3e\uff0c\u5c06 *found \u8bbe\u4e3a true \u5e76\u8fd4\u56de\u3002\u627e\u4e0d\u5230\u65f6\uff0c\u65b0\u5efa\u6761\u76ee\u5e76\u52a0\u5165\u5217\u8868\u672b\u5c3e\uff0c\u540c\u65f6\u66f4\u65b0\u7f13\u5b58\u5185\u5b58\u8bb0\u8d26\uff1b\u5982\u679c\u8d85\u51fa\u9884\u7b97\u5219\u79fb\u9664\u8f83\u65e7\u6761\u76ee\u3002\u6210\u529f\u91ca\u653e\u8db3\u591f\u5185\u5b58\u65f6\u8fd4\u56de\u65b0\u6761\u76ee\uff0c\u5426\u5219\u8fd4\u56de NULL\u3002", "\u5982\u679c\u8d85\u51fa\u5185\u5b58\u9884\u7b97\uff0c\u5c31\u91ca\u653e\u90e8\u5206\u7f13\u5b58\u7a7a\u95f4\u3002"]}, {"title": "\u5e76\u884c\u6267\u884c\u4e0e\u8fd0\u884c\u4fe1\u606f\u91c7\u96c6", "paragraphs": ["\u4ee5\u4e0b\u6e90\u7801\u56de\u8c03\u53ef\u4ee5\u534f\u8c03\u6267\u884c\u6216\u6536\u96c6\u5de5\u4f5c\u8fdb\u7a0b\u7684\u6d4b\u91cf\u6570\u636e\u3002\u56de\u8c03\u5b58\u5728\u4e0d\u4ee3\u8868\u8be5\u8282\u70b9\u666e\u904d\u652f\u6301\u5171\u4eab\u5e76\u884c\u626b\u63cf\u6216\u5171\u4eab\u72b6\u6001\u3002", "\u6b64\u6784\u5efa\u7684\u56de\u8c03\uff1aExecMemoizeEstimate, ExecMemoizeInitializeDSM, ExecMemoizeInitializeWorker, ExecMemoizeRetrieveInstrumentation."]}, {"title": "\u6267\u884c\u5668\u5b9e\u73b0\u8bf4\u660e", "paragraphs": ["nodeMemoize.c\uff1a\u7f13\u5b58\u53c2\u6570\u5316\u8282\u70b9\u7ed3\u679c\u7684\u4f8b\u7a0b\u3002", "Memoize \u8282\u70b9\u4f4d\u4e8e\u8ba1\u5212\u6811\u4e2d\u7684\u53c2\u6570\u5316\u8282\u70b9\u4e4b\u4e0a\uff0c\u7528\u6765\u7f13\u5b58\u5176\u7ed3\u679c\u3002\u4f7f\u7528\u5df2\u7ecf\u51fa\u73b0\u8fc7\u7684\u53c2\u6570\u503c\u518d\u6b21\u626b\u63cf\u65f6\uff0c\u53ef\u4ee5\u76f4\u63a5\u4ece\u7f13\u5b58\u8bfb\u53d6\u5143\u7ec4\uff0c\u65e0\u9700\u91cd\u65b0\u626b\u63cf\u5185\u90e8\u8282\u70b9\u3002\u89c4\u5212\u5668\u8ba4\u4e3a\u91cd\u590d\u4f7f\u7528\u5df2\u6709\u53c2\u6570\u503c\u7684\u6982\u7387\u8db3\u4ee5\u62b5\u507f\u989d\u5916\u8282\u70b9\u5f00\u9500\u65f6\uff0c\u53ef\u80fd\u9009\u62e9\u8fd9\u79cd\u8282\u70b9\u3002", "\u7f13\u5b58\u91c7\u7528\u54c8\u5e0c\u8868\u3002\u7f13\u5b58\u7528\u6ee1\u65f6\u4e0d\u4f1a\u5c06\u5143\u7ec4\u5199\u5165\u78c1\u76d8\uff0c\u800c\u662f\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u7f13\u5b58\u6761\u76ee\u3002\u901a\u8fc7\u628a\u65b0\u6761\u76ee\u548c\u6bcf\u6b21\u67e5\u627e\u7684\u6761\u76ee\u79fb\u5230\u53cc\u5411\u94fe\u8868\u672b\u5c3e\u6765\u8bb0\u5f55\u4f7f\u7528\u6b21\u5e8f\uff0c\u4f7f\u8f83\u4e45\u672a\u7528\u7684\u6761\u76ee\u9010\u6e10\u79fb\u5230 LRU \u5217\u8868\u5934\u90e8\u3002", "\u8c03\u7528\u8005\u6709\u65f6\u4e0d\u4f1a\u5b8c\u6210\u5168\u90e8\u626b\u63cf\u3002\u4f8b\u5982\uff0c\u534a\u8fde\u63a5\u627e\u5230\u4e00\u4e2a\u5339\u914d\u5143\u7ec4\u540e\u5c31\u8df3\u5230\u4e0b\u4e00\u4e2a\u5916\u4fa7\u5143\u7ec4\uff0c\u4e0d\u518d\u7ee7\u7eed\u6b64\u6b21\u5185\u4fa7\u626b\u63cf\u3002\u56e0\u6b64\u5fc5\u987b\u8ddf\u8e2a\u7f13\u5b58\u6761\u76ee\u662f\u5426\u5b8c\u6574\uff1b\u901a\u5e38\u626b\u63cf\u8bfb\u4e0d\u5230\u66f4\u591a\u5143\u7ec4\u65f6\u624d\u53ef\u786e\u8ba4\u5b8c\u6574\u3002\u4e0d\u8fc7\u67d0\u4e9b\u60c5\u51b5\u4e0b\u4e0d\u5fc5\u8017\u5c3d\u626b\u63cf\u4fbf\u80fd\u6807\u8bb0\u5b8c\u6574\u3002\u4f8b\u5982\u552f\u4e00\u8fde\u63a5\u5df2\u77e5\u6bcf\u4e2a\u5916\u4fa7\u5143\u7ec4\u6700\u591a\u5339\u914d\u4e00\u884c\u3002\u4e3a\u652f\u6301\u8fd9\u79cd\u60c5\u51b5\uff0c\u7f13\u5b58\u5141\u8bb8\u8bbe\u7f6e singlerow \u9009\u9879\uff0c\u5728\u4ece\u5b50\u8282\u70b9\u8bfb\u53d6\u7b2c\u4e00\u4e2a\u5143\u7ec4\u540e\u5373\u6807\u8bb0\u8be5\u7f13\u5b58\u6761\u76ee\u5b8c\u6574\u3002", "\u4e3a\u4e00\u7ec4\u53c2\u6570\u586b\u5145\u7f13\u5b58\u65f6\uff0c\u53ef\u80fd\u65e0\u6cd5\u91ca\u653e\u8db3\u591f\u7684\u5185\u5b58\u6765\u5b58\u50a8\u66f4\u591a\u5143\u7ec4\u3002\u6b64\u65f6\u5176\u4ed6\u7f13\u5b58\u6761\u76ee\u5df2\u7ecf\u5168\u90e8\u88ab\u6dd8\u6c70\u3002\u5982\u679c\u518d\u7f13\u5b58\u4e00\u4e2a\u5143\u7ec4\u4f1a\u8d85\u51fa\u9884\u7b97\uff0c\u5fc5\u987b\u91ca\u653e\u6b63\u5728\u586b\u5145\u7684\u6761\u76ee\uff0c\u5e76\u5c06\u72b6\u6001\u673a\u5207\u6362\u4e3a MEMO_CACHE_BYPASS_MODE\u3002\u8fd9\u610f\u5473\u7740\u672c\u6b21\u626b\u63cf\u4e0d\u518d\u5c1d\u8bd5\u7f13\u5b58\u540e\u7eed\u5143\u7ec4\uff0c\u56e0\u4e3a\u5185\u5b58\u4e0d\u8db3\u3002\u4e0b\u6b21\u91cd\u65b0\u626b\u63cf\u65f6\u72b6\u6001\u673a\u4f1a\u91cd\u7f6e\uff1b\u5982\u679c\u4e0b\u4e00\u7ec4\u53c2\u6570\u7684\u5143\u7ec4\u7f13\u5b58\u9700\u8981\u8f83\u5c11\u5185\u5b58\uff0c\u5c31\u53ef\u80fd\u91cd\u65b0\u5f00\u59cb\u7f13\u5b58\u6709\u7528\u7684\u6761\u76ee\u3002"]}, {"code": "case T_Memoize:\n\t\t\tpname = sname = \"Memoize\";\n\t\t\tbreak;", "title": "\u6838\u5fc3\u6e90\u7801\u4e2d\u7684 EXPLAIN \u6807\u8bc6"}], "strategies": [], "description": ["\u7f13\u5b58\u53c2\u6570\u5316\u5b50\u8ba1\u5212\u7684\u7ed3\u679c\uff0c\u5e76\u5728\u76f8\u540c\u53c2\u6570\u503c\u518d\u6b21\u51fa\u73b0\u65f6\u590d\u7528\u3002"], "localization": {"status": "complete", "sources": [], "language": "zh", "original_text": {"/versions/19/description/0": "Caches results from a parameterized child and reuses them when the same parameter values recur.", "/versions/19/facts/0/label": "Core node tag", "/versions/19/facts/1/label": "Structured EXPLAIN Node Type", "/versions/19/facts/2/label": "Inputs", "/versions/19/facts/2/value": "One parameterized child plan", "/versions/19/facts/3/label": "Output", "/versions/19/facts/3/value": "Cached or newly produced child tuples", "/versions/19/facts/4/label": "Executor initializer", "/versions/19/facts/5/label": "Memory mechanism", "/versions/19/tables/0/title": "EXPLAIN labels in this source build", "/versions/19/related/1/label": "Using EXPLAIN", "/versions/19/related/2/label": "Parallel plans", "/versions/19/sections/0/title": "EXPLAIN names and attributes", "/versions/19/sections/1/title": "Memory and temporary storage", "/versions/19/sections/2/title": "Parallel execution and instrumentation", "/versions/19/sections/3/title": "Executor implementation notes", "/versions/19/sections/4/title": "EXPLAIN identity in core source", "/versions/19/memory/description": "The Memoize cache evicts least-recently-used entries instead of spilling cached tuples to disk. If a result cannot fit, caching can enter bypass mode for that scan.", "/versions/19/sections/0/paragraphs/0": "Structured formats use the Node Type above. Text-format spellings can also include operation, strategy, join type, scan direction or aggregation-stage attributes.", "/versions/19/sections/0/paragraphs/1": "Text names recorded by this source: Memoize.", "/versions/19/sections/0/paragraphs/2": "Parallel-aware and parallel-safe are different plan properties. A node running inside a parallel worker is not necessarily a parallel-aware node.", "/versions/19/sections/1/paragraphs/0": "The Memoize cache evicts least-recently-used entries instead of spilling cached tuples to disk. If a result cannot fit, caching can enter bypass mode for that scan.", "/versions/19/sections/1/paragraphs/1": "The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.", "/versions/19/sections/1/paragraphs/2": "It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.", "/versions/19/sections/1/paragraphs/3": "cache_lookup Perform a lookup to see if we've already cached tuples based on the scan's current parameters. If we find an existing entry we move it to the end of the LRU list, set *found to true then return it. If we don't find an entry then we create a new one and add it to the end of the LRU list. We also update cache memory accounting and remove older entries if we go over the memory budget. If we managed to free enough memory we return the new entry, else we return NULL.", "/versions/19/sections/1/paragraphs/4": "If we've gone over our memory budget, then we'll free up some space in the cache.", "/versions/19/sections/2/paragraphs/0": "The source callbacks below can coordinate execution or collect worker instrumentation. Their presence is not a blanket claim that this node supports a shared parallel scan or shared state.", "/versions/19/sections/2/paragraphs/1": "Callbacks in this build: ExecMemoizeEstimate, ExecMemoizeInitializeDSM, ExecMemoizeInitializeWorker, ExecMemoizeRetrieveInstrumentation.", "/versions/19/sections/3/paragraphs/0": "nodeMemoize.c Routines to handle caching of results from parameterized nodes", "/versions/19/sections/3/paragraphs/1": "Memoize nodes are intended to sit above parameterized nodes in the plan tree in order to cache results from them. The intention here is that a repeat scan with a parameter value that has already been seen by the node can fetch tuples from the cache rather than having to re-scan the inner node all over again. The query planner may choose to make use of one of these when it thinks rescans for previously seen values are likely enough to warrant adding the additional node.", "/versions/19/sections/3/paragraphs/2": "The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.", "/versions/19/sections/3/paragraphs/3": "Sometimes our callers won't run their scans to completion. For example a semi-join only needs to run until it finds a matching tuple, and once it does, the join operator skips to the next outer tuple and does not execute the inner side again on that scan. Because of this, we must keep track of when a cache entry is complete, and by default, we know it is when we run out of tuples to read during the scan. However, there are cases where we can mark the cache entry as complete without exhausting the scan of all tuples. One case is unique joins, where the join operator knows that there will only be at most one match for any given outer tuple. In order to support such cases we allow the \"singlerow\" option to be set for the cache. This option marks the cache entry as complete after we read the first tuple from the subnode.", "/versions/19/sections/3/paragraphs/4": "It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.", "/versions/19/tables/0/columns/0/label": "Text-format label", "/versions/19/tables/0/columns/1/label": "Structured node identity"}, "fallback_fields": [], "source_language": "en", "original_snapshot_sha256": "fcba51d16ffbcd855c421a1721b901ac39b3be0b891a6cd73cd58f4c2a08beb8"}, "evidence_kind": "source and documentation", "explain_names": ["Memoize"], "partial_modes": [], "comparison_data": {"node_tag": "T_Memoize", "strategies": [], "text_names": ["Memoize"], "initializer": "ExecInitMemoize", "partial_modes": [], "memory_mechanism": "eviction", "parallel_callbacks": ["ExecMemoizeEstimate", "ExecMemoizeInitializeDSM", "ExecMemoizeInitializeWorker", "ExecMemoizeRetrieveInstrumentation"]}, "comparison_hash": "ce79083064802c6772e741ce60e19e9c932b05d1f1a5ed00e8f8f2e86d678f5b", "explain_prefixes": ["Parallel", "Async"], "runtime_verified": false, "source_inventory": {"explain": "src/backend/commands/explain.c", "executor": "src/backend/executor/execProcnode.c", "implementation": "src/backend/executor/nodeMemoize.c"}, "parallel_callbacks": ["ExecMemoizeEstimate", "ExecMemoizeInitializeDSM", "ExecMemoizeInitializeWorker", "ExecMemoizeRetrieveInstrumentation"]}, "20": {"facts": [{"label": "\u6838\u5fc3\u8282\u70b9\u6807\u7b7e", "value": "T_Memoize"}, {"label": "\u7ed3\u6784\u5316 EXPLAIN \u8282\u70b9\u7c7b\u578b", "value": "Memoize"}, {"label": "\u8f93\u5165", "value": "\u4e00\u4e2a\u53c2\u6570\u5316\u5b50\u8ba1\u5212"}, {"label": "\u8f93\u51fa", "value": "\u5df2\u7f13\u5b58\u6216\u65b0\u4ea7\u751f\u7684\u5b50\u8ba1\u5212\u5143\u7ec4"}, {"label": "\u6267\u884c\u5668\u521d\u59cb\u5316\u51fd\u6570", "value": "ExecInitMemoize"}, {"label": "\u5185\u5b58\u673a\u5236", "value": "eviction"}], "memory": {"evidence": [{"url": "https://ftp.postgresql.org/pub/snapshot/dev/postgresql-snapshot.tar.bz2", "path": "src/backend/executor/nodeMemoize.c", "label": "src/backend/executor/nodeMemoize.c", "sha256": "e120ca0070bfc617c3b129ec82ec5695accbb554dfebed46b275ddf2835949f6", "archive_sha256": "4d3346909b201ac1648232cf290462a7070c119326f56196f1f0253ed80fae41"}], "mechanism": "eviction", "description": "Memoize \u7f13\u5b58\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u6761\u76ee\uff0c\u4e0d\u5c06\u7f13\u5b58\u5143\u7ec4\u5199\u5165\u78c1\u76d8\u3002\u5982\u679c\u7ed3\u679c\u65e0\u6cd5\u653e\u5165\u7f13\u5b58\uff0c\u8be5\u6b21\u626b\u63cf\u53ef\u4ee5\u8fdb\u5165\u65c1\u8def\u6a21\u5f0f\u3002", "source_notes": ["The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.", "It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.", "cache_lookup Perform a lookup to see if we've already cached tuples based on the scan's current parameters. If we find an existing entry we move it to the end of the LRU list, set *found to true then return it. If we don't find an entry then we create a new one and add it to the end of the LRU list. We also update cache memory accounting and remove older entries if we go over the memory budget. If we managed to free enough memory we return the new entry, else we return NULL.", "If we've gone over our memory budget, then we'll free up some space in the cache."]}, "tables": [{"key": "explain-labels", "rows": [{"label": "Memoize", "identity": "Memoize"}], "title": "\u672c\u6784\u5efa\u4e2d\u7684 EXPLAIN \u6807\u7b7e", "columns": [{"key": "label", "label": "\u6587\u672c\u683c\u5f0f\u6807\u7b7e"}, {"key": "identity", "label": "\u7ed3\u6784\u5316\u8282\u70b9\u6807\u8bc6"}]}], "related": [{"url": "/wiki/sql/explain/?v=20", "label": "EXPLAIN"}, {"url": "/docs/devel/using-explain.html", "label": "\u4f7f\u7528 EXPLAIN"}, {"url": "/docs/devel/parallel-plans.html", "label": "\u5e76\u884c\u8ba1\u5212"}, {"url": "/wiki/guc/enable_memoize/?v=20", "label": "enable_memoize"}, {"url": "/wiki/guc/hash_mem_multiplier/?v=20", "label": "hash_mem_multiplier"}], "release": {"ref": "PostgreSQL 20devel source archive", "label": "20devel", "major": "20", "channel": "devel", "revision": "4d3346909b201ac1648232cf290462a7070c119326f56196f1f0253ed80fae41", "source_url": "https://ftp.postgresql.org/pub/snapshot/dev/postgresql-snapshot.tar.bz2", "source_snapshot_utc": "26-Sep-2026 20:22"}, "sources": [{"url": "https://ftp.postgresql.org/pub/snapshot/dev/postgresql-snapshot.tar.bz2", "line": 1531, "path": "src/backend/commands/explain.c", "label": "src/backend/commands/explain.c:1531", "sha256": "13402758013520451539427b5993db06d463ca11c4e2d4cc5444e82367688077", "archive_sha256": "4d3346909b201ac1648232cf290462a7070c119326f56196f1f0253ed80fae41"}, {"url": "https://ftp.postgresql.org/pub/snapshot/dev/postgresql-snapshot.tar.bz2", "line": 330, "path": "src/backend/executor/execProcnode.c", "label": "src/backend/executor/execProcnode.c:330", "sha256": "5e39b2037bed672da55104229ecc32da5abde44c26bcad01479edcfa044d09ed", "archive_sha256": "4d3346909b201ac1648232cf290462a7070c119326f56196f1f0253ed80fae41"}, {"url": "https://ftp.postgresql.org/pub/snapshot/dev/postgresql-snapshot.tar.bz2", "path": "src/backend/executor/nodeMemoize.c", "label": "src/backend/executor/nodeMemoize.c", "sha256": "e120ca0070bfc617c3b129ec82ec5695accbb554dfebed46b275ddf2835949f6", "archive_sha256": "4d3346909b201ac1648232cf290462a7070c119326f56196f1f0253ed80fae41"}, {"url": "https://ftp.postgresql.org/pub/snapshot/dev/postgresql-snapshot.tar.bz2", "path": "src/include/nodes/plannodes.h", "label": "src/include/nodes/plannodes.h", "sha256": "7a94ed1652f0d74d50c39971d1cd3e8051dbc0d6058f31b6de71a433ca343521", "archive_sha256": "4d3346909b201ac1648232cf290462a7070c119326f56196f1f0253ed80fae41"}], "node_tag": "T_Memoize", "sections": [{"title": "EXPLAIN \u540d\u79f0\u4e0e\u5c5e\u6027", "paragraphs": ["\u7ed3\u6784\u5316\u683c\u5f0f\u4f7f\u7528\u4e0a\u8ff0 Node Type\u3002\u6587\u672c\u683c\u5f0f\u540d\u79f0\u8fd8\u53ef\u80fd\u5305\u542b\u64cd\u4f5c\u3001\u7b56\u7565\u3001\u8fde\u63a5\u7c7b\u578b\u3001\u626b\u63cf\u65b9\u5411\u6216\u805a\u5408\u9636\u6bb5\u5c5e\u6027\u3002", "\u6b64\u6e90\u7801\u8bb0\u5f55\u7684\u6587\u672c\u540d\u79f0\uff1aMemoize.", "\u5e76\u884c\u611f\u77e5\u4e0e\u5e76\u884c\u5b89\u5168\u662f\u4e0d\u540c\u7684\u8ba1\u5212\u5c5e\u6027\u3002\u5728\u5e76\u884c\u5de5\u4f5c\u8fdb\u7a0b\u5185\u8fd0\u884c\u7684\u8282\u70b9\u4e0d\u4e00\u5b9a\u662f\u5e76\u884c\u611f\u77e5\u8282\u70b9\u3002"]}, {"title": "\u5185\u5b58\u4e0e\u4e34\u65f6\u5b58\u50a8", "paragraphs": ["Memoize \u7f13\u5b58\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u6761\u76ee\uff0c\u4e0d\u5c06\u7f13\u5b58\u5143\u7ec4\u5199\u5165\u78c1\u76d8\u3002\u5982\u679c\u7ed3\u679c\u65e0\u6cd5\u653e\u5165\u7f13\u5b58\uff0c\u8be5\u6b21\u626b\u63cf\u53ef\u4ee5\u8fdb\u5165\u65c1\u8def\u6a21\u5f0f\u3002", "\u7f13\u5b58\u91c7\u7528\u54c8\u5e0c\u8868\u3002\u7f13\u5b58\u7528\u6ee1\u65f6\u4e0d\u4f1a\u5c06\u5143\u7ec4\u5199\u5165\u78c1\u76d8\uff0c\u800c\u662f\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u7f13\u5b58\u6761\u76ee\u3002\u901a\u8fc7\u628a\u65b0\u6761\u76ee\u548c\u6bcf\u6b21\u67e5\u627e\u7684\u6761\u76ee\u79fb\u5230\u53cc\u5411\u94fe\u8868\u672b\u5c3e\u6765\u8bb0\u5f55\u4f7f\u7528\u6b21\u5e8f\uff0c\u4f7f\u8f83\u4e45\u672a\u7528\u7684\u6761\u76ee\u9010\u6e10\u79fb\u5230 LRU \u5217\u8868\u5934\u90e8\u3002", "\u4e3a\u4e00\u7ec4\u53c2\u6570\u586b\u5145\u7f13\u5b58\u65f6\uff0c\u53ef\u80fd\u65e0\u6cd5\u91ca\u653e\u8db3\u591f\u7684\u5185\u5b58\u6765\u5b58\u50a8\u66f4\u591a\u5143\u7ec4\u3002\u6b64\u65f6\u5176\u4ed6\u7f13\u5b58\u6761\u76ee\u5df2\u7ecf\u5168\u90e8\u88ab\u6dd8\u6c70\u3002\u5982\u679c\u518d\u7f13\u5b58\u4e00\u4e2a\u5143\u7ec4\u4f1a\u8d85\u51fa\u9884\u7b97\uff0c\u5fc5\u987b\u91ca\u653e\u6b63\u5728\u586b\u5145\u7684\u6761\u76ee\uff0c\u5e76\u5c06\u72b6\u6001\u673a\u5207\u6362\u4e3a MEMO_CACHE_BYPASS_MODE\u3002\u8fd9\u610f\u5473\u7740\u672c\u6b21\u626b\u63cf\u4e0d\u518d\u5c1d\u8bd5\u7f13\u5b58\u540e\u7eed\u5143\u7ec4\uff0c\u56e0\u4e3a\u5185\u5b58\u4e0d\u8db3\u3002\u4e0b\u6b21\u91cd\u65b0\u626b\u63cf\u65f6\u72b6\u6001\u673a\u4f1a\u91cd\u7f6e\uff1b\u5982\u679c\u4e0b\u4e00\u7ec4\u53c2\u6570\u7684\u5143\u7ec4\u7f13\u5b58\u9700\u8981\u8f83\u5c11\u5185\u5b58\uff0c\u5c31\u53ef\u80fd\u91cd\u65b0\u5f00\u59cb\u7f13\u5b58\u6709\u7528\u7684\u6761\u76ee\u3002", "cache_lookup \u6309\u626b\u63cf\u7684\u5f53\u524d\u53c2\u6570\u67e5\u627e\u662f\u5426\u5df2\u7f13\u5b58\u5143\u7ec4\u3002\u627e\u5230\u73b0\u6709\u6761\u76ee\u65f6\uff0c\u5c06\u5176\u79fb\u5230 LRU \u5217\u8868\u672b\u5c3e\uff0c\u5c06 *found \u8bbe\u4e3a true \u5e76\u8fd4\u56de\u3002\u627e\u4e0d\u5230\u65f6\uff0c\u65b0\u5efa\u6761\u76ee\u5e76\u52a0\u5165\u5217\u8868\u672b\u5c3e\uff0c\u540c\u65f6\u66f4\u65b0\u7f13\u5b58\u5185\u5b58\u8bb0\u8d26\uff1b\u5982\u679c\u8d85\u51fa\u9884\u7b97\u5219\u79fb\u9664\u8f83\u65e7\u6761\u76ee\u3002\u6210\u529f\u91ca\u653e\u8db3\u591f\u5185\u5b58\u65f6\u8fd4\u56de\u65b0\u6761\u76ee\uff0c\u5426\u5219\u8fd4\u56de NULL\u3002", "\u5982\u679c\u8d85\u51fa\u5185\u5b58\u9884\u7b97\uff0c\u5c31\u91ca\u653e\u90e8\u5206\u7f13\u5b58\u7a7a\u95f4\u3002"]}, {"title": "\u5e76\u884c\u6267\u884c\u4e0e\u8fd0\u884c\u4fe1\u606f\u91c7\u96c6", "paragraphs": ["\u4ee5\u4e0b\u6e90\u7801\u56de\u8c03\u53ef\u4ee5\u534f\u8c03\u6267\u884c\u6216\u6536\u96c6\u5de5\u4f5c\u8fdb\u7a0b\u7684\u6d4b\u91cf\u6570\u636e\u3002\u56de\u8c03\u5b58\u5728\u4e0d\u4ee3\u8868\u8be5\u8282\u70b9\u666e\u904d\u652f\u6301\u5171\u4eab\u5e76\u884c\u626b\u63cf\u6216\u5171\u4eab\u72b6\u6001\u3002", "\u6b64\u6784\u5efa\u7684\u56de\u8c03\uff1aExecMemoizeEstimate, ExecMemoizeInitializeDSM, ExecMemoizeInitializeWorker, ExecMemoizeRetrieveInstrumentation."]}, {"title": "\u6267\u884c\u5668\u5b9e\u73b0\u8bf4\u660e", "paragraphs": ["nodeMemoize.c\uff1a\u7f13\u5b58\u53c2\u6570\u5316\u8282\u70b9\u7ed3\u679c\u7684\u4f8b\u7a0b\u3002", "Memoize \u8282\u70b9\u4f4d\u4e8e\u8ba1\u5212\u6811\u4e2d\u7684\u53c2\u6570\u5316\u8282\u70b9\u4e4b\u4e0a\uff0c\u7528\u6765\u7f13\u5b58\u5176\u7ed3\u679c\u3002\u4f7f\u7528\u5df2\u7ecf\u51fa\u73b0\u8fc7\u7684\u53c2\u6570\u503c\u518d\u6b21\u626b\u63cf\u65f6\uff0c\u53ef\u4ee5\u76f4\u63a5\u4ece\u7f13\u5b58\u8bfb\u53d6\u5143\u7ec4\uff0c\u65e0\u9700\u91cd\u65b0\u626b\u63cf\u5185\u90e8\u8282\u70b9\u3002\u89c4\u5212\u5668\u8ba4\u4e3a\u91cd\u590d\u4f7f\u7528\u5df2\u6709\u53c2\u6570\u503c\u7684\u6982\u7387\u8db3\u4ee5\u62b5\u507f\u989d\u5916\u8282\u70b9\u5f00\u9500\u65f6\uff0c\u53ef\u80fd\u9009\u62e9\u8fd9\u79cd\u8282\u70b9\u3002", "\u7f13\u5b58\u91c7\u7528\u54c8\u5e0c\u8868\u3002\u7f13\u5b58\u7528\u6ee1\u65f6\u4e0d\u4f1a\u5c06\u5143\u7ec4\u5199\u5165\u78c1\u76d8\uff0c\u800c\u662f\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u7f13\u5b58\u6761\u76ee\u3002\u901a\u8fc7\u628a\u65b0\u6761\u76ee\u548c\u6bcf\u6b21\u67e5\u627e\u7684\u6761\u76ee\u79fb\u5230\u53cc\u5411\u94fe\u8868\u672b\u5c3e\u6765\u8bb0\u5f55\u4f7f\u7528\u6b21\u5e8f\uff0c\u4f7f\u8f83\u4e45\u672a\u7528\u7684\u6761\u76ee\u9010\u6e10\u79fb\u5230 LRU \u5217\u8868\u5934\u90e8\u3002", "\u8c03\u7528\u8005\u6709\u65f6\u4e0d\u4f1a\u5b8c\u6210\u5168\u90e8\u626b\u63cf\u3002\u4f8b\u5982\uff0c\u534a\u8fde\u63a5\u627e\u5230\u4e00\u4e2a\u5339\u914d\u5143\u7ec4\u540e\u5c31\u8df3\u5230\u4e0b\u4e00\u4e2a\u5916\u4fa7\u5143\u7ec4\uff0c\u4e0d\u518d\u7ee7\u7eed\u6b64\u6b21\u5185\u4fa7\u626b\u63cf\u3002\u56e0\u6b64\u5fc5\u987b\u8ddf\u8e2a\u7f13\u5b58\u6761\u76ee\u662f\u5426\u5b8c\u6574\uff1b\u901a\u5e38\u626b\u63cf\u8bfb\u4e0d\u5230\u66f4\u591a\u5143\u7ec4\u65f6\u624d\u53ef\u786e\u8ba4\u5b8c\u6574\u3002\u4e0d\u8fc7\u67d0\u4e9b\u60c5\u51b5\u4e0b\u4e0d\u5fc5\u8017\u5c3d\u626b\u63cf\u4fbf\u80fd\u6807\u8bb0\u5b8c\u6574\u3002\u4f8b\u5982\u552f\u4e00\u8fde\u63a5\u5df2\u77e5\u6bcf\u4e2a\u5916\u4fa7\u5143\u7ec4\u6700\u591a\u5339\u914d\u4e00\u884c\u3002\u4e3a\u652f\u6301\u8fd9\u79cd\u60c5\u51b5\uff0c\u7f13\u5b58\u5141\u8bb8\u8bbe\u7f6e singlerow \u9009\u9879\uff0c\u5728\u4ece\u5b50\u8282\u70b9\u8bfb\u53d6\u7b2c\u4e00\u4e2a\u5143\u7ec4\u540e\u5373\u6807\u8bb0\u8be5\u7f13\u5b58\u6761\u76ee\u5b8c\u6574\u3002", "\u4e3a\u4e00\u7ec4\u53c2\u6570\u586b\u5145\u7f13\u5b58\u65f6\uff0c\u53ef\u80fd\u65e0\u6cd5\u91ca\u653e\u8db3\u591f\u7684\u5185\u5b58\u6765\u5b58\u50a8\u66f4\u591a\u5143\u7ec4\u3002\u6b64\u65f6\u5176\u4ed6\u7f13\u5b58\u6761\u76ee\u5df2\u7ecf\u5168\u90e8\u88ab\u6dd8\u6c70\u3002\u5982\u679c\u518d\u7f13\u5b58\u4e00\u4e2a\u5143\u7ec4\u4f1a\u8d85\u51fa\u9884\u7b97\uff0c\u5fc5\u987b\u91ca\u653e\u6b63\u5728\u586b\u5145\u7684\u6761\u76ee\uff0c\u5e76\u5c06\u72b6\u6001\u673a\u5207\u6362\u4e3a MEMO_CACHE_BYPASS_MODE\u3002\u8fd9\u610f\u5473\u7740\u672c\u6b21\u626b\u63cf\u4e0d\u518d\u5c1d\u8bd5\u7f13\u5b58\u540e\u7eed\u5143\u7ec4\uff0c\u56e0\u4e3a\u5185\u5b58\u4e0d\u8db3\u3002\u4e0b\u6b21\u91cd\u65b0\u626b\u63cf\u65f6\u72b6\u6001\u673a\u4f1a\u91cd\u7f6e\uff1b\u5982\u679c\u4e0b\u4e00\u7ec4\u53c2\u6570\u7684\u5143\u7ec4\u7f13\u5b58\u9700\u8981\u8f83\u5c11\u5185\u5b58\uff0c\u5c31\u53ef\u80fd\u91cd\u65b0\u5f00\u59cb\u7f13\u5b58\u6709\u7528\u7684\u6761\u76ee\u3002"]}, {"code": "case T_Memoize:\n\t\t\tpname = sname = \"Memoize\";\n\t\t\tbreak;", "title": "\u6838\u5fc3\u6e90\u7801\u4e2d\u7684 EXPLAIN \u6807\u8bc6"}], "strategies": [], "description": ["\u7f13\u5b58\u53c2\u6570\u5316\u5b50\u8ba1\u5212\u7684\u7ed3\u679c\uff0c\u5e76\u5728\u76f8\u540c\u53c2\u6570\u503c\u518d\u6b21\u51fa\u73b0\u65f6\u590d\u7528\u3002"], "localization": {"status": "complete", "sources": [], "language": "zh", "original_text": {"/summary": "Caches results from a parameterized child and reuses them when the same parameter values recur.", "/category": "Materialization", "/versions/20/description/0": "Caches results from a parameterized child and reuses them when the same parameter values recur.", "/versions/20/facts/0/label": "Core node tag", "/versions/20/facts/1/label": "Structured EXPLAIN Node Type", "/versions/20/facts/2/label": "Inputs", "/versions/20/facts/2/value": "One parameterized child plan", "/versions/20/facts/3/label": "Output", "/versions/20/facts/3/value": "Cached or newly produced child tuples", "/versions/20/facts/4/label": "Executor initializer", "/versions/20/facts/5/label": "Memory mechanism", "/versions/20/tables/0/title": "EXPLAIN labels in this source build", "/versions/20/related/1/label": "Using EXPLAIN", "/versions/20/related/2/label": "Parallel plans", "/versions/20/sections/0/title": "EXPLAIN names and attributes", "/versions/20/sections/1/title": "Memory and temporary storage", "/versions/20/sections/2/title": "Parallel execution and instrumentation", "/versions/20/sections/3/title": "Executor implementation notes", "/versions/20/sections/4/title": "EXPLAIN identity in core source", "/versions/20/memory/description": "The Memoize cache evicts least-recently-used entries instead of spilling cached tuples to disk. If a result cannot fit, caching can enter bypass mode for that scan.", "/versions/20/sections/0/paragraphs/0": "Structured formats use the Node Type above. Text-format spellings can also include operation, strategy, join type, scan direction or aggregation-stage attributes.", "/versions/20/sections/0/paragraphs/1": "Text names recorded by this source: Memoize.", "/versions/20/sections/0/paragraphs/2": "Parallel-aware and parallel-safe are different plan properties. A node running inside a parallel worker is not necessarily a parallel-aware node.", "/versions/20/sections/1/paragraphs/0": "The Memoize cache evicts least-recently-used entries instead of spilling cached tuples to disk. If a result cannot fit, caching can enter bypass mode for that scan.", "/versions/20/sections/1/paragraphs/1": "The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.", "/versions/20/sections/1/paragraphs/2": "It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.", "/versions/20/sections/1/paragraphs/3": "cache_lookup Perform a lookup to see if we've already cached tuples based on the scan's current parameters. If we find an existing entry we move it to the end of the LRU list, set *found to true then return it. If we don't find an entry then we create a new one and add it to the end of the LRU list. We also update cache memory accounting and remove older entries if we go over the memory budget. If we managed to free enough memory we return the new entry, else we return NULL.", "/versions/20/sections/1/paragraphs/4": "If we've gone over our memory budget, then we'll free up some space in the cache.", "/versions/20/sections/2/paragraphs/0": "The source callbacks below can coordinate execution or collect worker instrumentation. Their presence is not a blanket claim that this node supports a shared parallel scan or shared state.", "/versions/20/sections/2/paragraphs/1": "Callbacks in this build: ExecMemoizeEstimate, ExecMemoizeInitializeDSM, ExecMemoizeInitializeWorker, ExecMemoizeRetrieveInstrumentation.", "/versions/20/sections/3/paragraphs/0": "nodeMemoize.c Routines to handle caching of results from parameterized nodes", "/versions/20/sections/3/paragraphs/1": "Memoize nodes are intended to sit above parameterized nodes in the plan tree in order to cache results from them. The intention here is that a repeat scan with a parameter value that has already been seen by the node can fetch tuples from the cache rather than having to re-scan the inner node all over again. The query planner may choose to make use of one of these when it thinks rescans for previously seen values are likely enough to warrant adding the additional node.", "/versions/20/sections/3/paragraphs/2": "The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.", "/versions/20/sections/3/paragraphs/3": "Sometimes our callers won't run their scans to completion. For example a semi-join only needs to run until it finds a matching tuple, and once it does, the join operator skips to the next outer tuple and does not execute the inner side again on that scan. Because of this, we must keep track of when a cache entry is complete, and by default, we know it is when we run out of tuples to read during the scan. However, there are cases where we can mark the cache entry as complete without exhausting the scan of all tuples. One case is unique joins, where the join operator knows that there will only be at most one match for any given outer tuple. In order to support such cases we allow the \"singlerow\" option to be set for the cache. This option marks the cache entry as complete after we read the first tuple from the subnode.", "/versions/20/sections/3/paragraphs/4": "It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.", "/versions/20/tables/0/columns/0/label": "Text-format label", "/versions/20/tables/0/columns/1/label": "Structured node identity"}, "fallback_fields": [], "source_language": "en", "original_snapshot_sha256": "75a3f909663624471156610d46611e203fdf0f0112d874e713093b9ad1618988"}, "evidence_kind": "source and documentation", "explain_names": ["Memoize"], "partial_modes": [], "comparison_data": {"node_tag": "T_Memoize", "strategies": [], "text_names": ["Memoize"], "initializer": "ExecInitMemoize", "partial_modes": [], "memory_mechanism": "eviction", "parallel_callbacks": ["ExecMemoizeEstimate", "ExecMemoizeInitializeDSM", "ExecMemoizeInitializeWorker", "ExecMemoizeRetrieveInstrumentation"]}, "comparison_hash": "ce79083064802c6772e741ce60e19e9c932b05d1f1a5ed00e8f8f2e86d678f5b", "explain_prefixes": ["Parallel", "Async"], "runtime_verified": false, "source_inventory": {"explain": "src/backend/commands/explain.c", "executor": "src/backend/executor/execProcnode.c", "implementation": "src/backend/executor/nodeMemoize.c"}, "parallel_callbacks": ["ExecMemoizeEstimate", "ExecMemoizeInitializeDSM", "ExecMemoizeInitializeWorker", "ExecMemoizeRetrieveInstrumentation"]}}}, "snapshot": {"facts": [{"label": "\u6838\u5fc3\u8282\u70b9\u6807\u7b7e", "value": "T_Memoize"}, {"label": "\u7ed3\u6784\u5316 EXPLAIN \u8282\u70b9\u7c7b\u578b", "value": "Memoize"}, {"label": "\u8f93\u5165", "value": "\u4e00\u4e2a\u53c2\u6570\u5316\u5b50\u8ba1\u5212"}, {"label": "\u8f93\u51fa", "value": "\u5df2\u7f13\u5b58\u6216\u65b0\u4ea7\u751f\u7684\u5b50\u8ba1\u5212\u5143\u7ec4"}, {"label": "\u6267\u884c\u5668\u521d\u59cb\u5316\u51fd\u6570", "value": "ExecInitMemoize"}, {"label": "\u5185\u5b58\u673a\u5236", "value": "eviction"}], "memory": {"evidence": [{"url": "https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2", "path": "src/backend/executor/nodeMemoize.c", "label": "src/backend/executor/nodeMemoize.c", "sha256": "1dec8adecc70763c97957ed94510c4734a399223fd2f81f5534a30351c06c5d6", "archive_sha256": "555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f"}], "mechanism": "eviction", "description": "Memoize \u7f13\u5b58\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u6761\u76ee\uff0c\u4e0d\u5c06\u7f13\u5b58\u5143\u7ec4\u5199\u5165\u78c1\u76d8\u3002\u5982\u679c\u7ed3\u679c\u65e0\u6cd5\u653e\u5165\u7f13\u5b58\uff0c\u8be5\u6b21\u626b\u63cf\u53ef\u4ee5\u8fdb\u5165\u65c1\u8def\u6a21\u5f0f\u3002", "source_notes": ["The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.", "It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.", "cache_lookup Perform a lookup to see if we've already cached tuples based on the scan's current parameters. If we find an existing entry we move it to the end of the LRU list, set *found to true then return it. If we don't find an entry then we create a new one and add it to the end of the LRU list. We also update cache memory accounting and remove older entries if we go over the memory budget. If we managed to free enough memory we return the new entry, else we return NULL.", "If we've gone over our memory budget, then we'll free up some space in the cache."]}, "tables": [{"key": "explain-labels", "rows": [{"label": "Memoize", "identity": "Memoize"}], "title": "\u672c\u6784\u5efa\u4e2d\u7684 EXPLAIN \u6807\u7b7e", "columns": [{"key": "label", "label": "\u6587\u672c\u683c\u5f0f\u6807\u7b7e"}, {"key": "identity", "label": "\u7ed3\u6784\u5316\u8282\u70b9\u6807\u8bc6"}]}], "related": [{"url": "/wiki/sql/explain/?v=18", "label": "EXPLAIN"}, {"url": "/docs/18/using-explain.html", "label": "\u4f7f\u7528 EXPLAIN"}, {"url": "/docs/18/parallel-plans.html", "label": "\u5e76\u884c\u8ba1\u5212"}, {"url": "/wiki/guc/enable_memoize/?v=18", "label": "enable_memoize"}, {"url": "/wiki/guc/hash_mem_multiplier/?v=18", "label": "hash_mem_multiplier"}], "release": {"ref": "PostgreSQL 18.6 source archive", "label": "18.6", "major": "18", "channel": "stable", "revision": "555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f", "source_url": "https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2", "source_snapshot_utc": ""}, "sources": [{"url": "https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2", "line": 1519, "path": "src/backend/commands/explain.c", "label": "src/backend/commands/explain.c:1519", "sha256": "34c86d6070224a0e981efef51f79101d6d505e5874f1684ace183034bab14bb4", "archive_sha256": "555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f"}, {"url": "https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2", "line": 330, "path": "src/backend/executor/execProcnode.c", "label": "src/backend/executor/execProcnode.c:330", "sha256": "f8a06a3f539077249b20664b2812433db6d7bd12b2c0ca633525db43d06f112a", "archive_sha256": "555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f"}, {"url": "https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2", "path": "src/backend/executor/nodeMemoize.c", "label": "src/backend/executor/nodeMemoize.c", "sha256": "1dec8adecc70763c97957ed94510c4734a399223fd2f81f5534a30351c06c5d6", "archive_sha256": "555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f"}, {"url": "https://ftp.postgresql.org/pub/source/v18.6/postgresql-18.6.tar.bz2", "path": "src/include/nodes/plannodes.h", "label": "src/include/nodes/plannodes.h", "sha256": "52422b327a8049fbbb20d8b96008a0fc0a6fafa60f7eff3c695d5b2e83830120", "archive_sha256": "555610c24d53e4316da5b7d3fc25c279d96856d5e0e23ee308c328c5fa881d9f"}], "node_tag": "T_Memoize", "sections": [{"title": "EXPLAIN \u540d\u79f0\u4e0e\u5c5e\u6027", "paragraphs": ["\u7ed3\u6784\u5316\u683c\u5f0f\u4f7f\u7528\u4e0a\u8ff0 Node Type\u3002\u6587\u672c\u683c\u5f0f\u540d\u79f0\u8fd8\u53ef\u80fd\u5305\u542b\u64cd\u4f5c\u3001\u7b56\u7565\u3001\u8fde\u63a5\u7c7b\u578b\u3001\u626b\u63cf\u65b9\u5411\u6216\u805a\u5408\u9636\u6bb5\u5c5e\u6027\u3002", "\u6b64\u6e90\u7801\u8bb0\u5f55\u7684\u6587\u672c\u540d\u79f0\uff1aMemoize.", "\u5e76\u884c\u611f\u77e5\u4e0e\u5e76\u884c\u5b89\u5168\u662f\u4e0d\u540c\u7684\u8ba1\u5212\u5c5e\u6027\u3002\u5728\u5e76\u884c\u5de5\u4f5c\u8fdb\u7a0b\u5185\u8fd0\u884c\u7684\u8282\u70b9\u4e0d\u4e00\u5b9a\u662f\u5e76\u884c\u611f\u77e5\u8282\u70b9\u3002"]}, {"title": "\u5185\u5b58\u4e0e\u4e34\u65f6\u5b58\u50a8", "paragraphs": ["Memoize \u7f13\u5b58\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u6761\u76ee\uff0c\u4e0d\u5c06\u7f13\u5b58\u5143\u7ec4\u5199\u5165\u78c1\u76d8\u3002\u5982\u679c\u7ed3\u679c\u65e0\u6cd5\u653e\u5165\u7f13\u5b58\uff0c\u8be5\u6b21\u626b\u63cf\u53ef\u4ee5\u8fdb\u5165\u65c1\u8def\u6a21\u5f0f\u3002", "\u7f13\u5b58\u91c7\u7528\u54c8\u5e0c\u8868\u3002\u7f13\u5b58\u7528\u6ee1\u65f6\u4e0d\u4f1a\u5c06\u5143\u7ec4\u5199\u5165\u78c1\u76d8\uff0c\u800c\u662f\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u7f13\u5b58\u6761\u76ee\u3002\u901a\u8fc7\u628a\u65b0\u6761\u76ee\u548c\u6bcf\u6b21\u67e5\u627e\u7684\u6761\u76ee\u79fb\u5230\u53cc\u5411\u94fe\u8868\u672b\u5c3e\u6765\u8bb0\u5f55\u4f7f\u7528\u6b21\u5e8f\uff0c\u4f7f\u8f83\u4e45\u672a\u7528\u7684\u6761\u76ee\u9010\u6e10\u79fb\u5230 LRU \u5217\u8868\u5934\u90e8\u3002", "\u4e3a\u4e00\u7ec4\u53c2\u6570\u586b\u5145\u7f13\u5b58\u65f6\uff0c\u53ef\u80fd\u65e0\u6cd5\u91ca\u653e\u8db3\u591f\u7684\u5185\u5b58\u6765\u5b58\u50a8\u66f4\u591a\u5143\u7ec4\u3002\u6b64\u65f6\u5176\u4ed6\u7f13\u5b58\u6761\u76ee\u5df2\u7ecf\u5168\u90e8\u88ab\u6dd8\u6c70\u3002\u5982\u679c\u518d\u7f13\u5b58\u4e00\u4e2a\u5143\u7ec4\u4f1a\u8d85\u51fa\u9884\u7b97\uff0c\u5fc5\u987b\u91ca\u653e\u6b63\u5728\u586b\u5145\u7684\u6761\u76ee\uff0c\u5e76\u5c06\u72b6\u6001\u673a\u5207\u6362\u4e3a MEMO_CACHE_BYPASS_MODE\u3002\u8fd9\u610f\u5473\u7740\u672c\u6b21\u626b\u63cf\u4e0d\u518d\u5c1d\u8bd5\u7f13\u5b58\u540e\u7eed\u5143\u7ec4\uff0c\u56e0\u4e3a\u5185\u5b58\u4e0d\u8db3\u3002\u4e0b\u6b21\u91cd\u65b0\u626b\u63cf\u65f6\u72b6\u6001\u673a\u4f1a\u91cd\u7f6e\uff1b\u5982\u679c\u4e0b\u4e00\u7ec4\u53c2\u6570\u7684\u5143\u7ec4\u7f13\u5b58\u9700\u8981\u8f83\u5c11\u5185\u5b58\uff0c\u5c31\u53ef\u80fd\u91cd\u65b0\u5f00\u59cb\u7f13\u5b58\u6709\u7528\u7684\u6761\u76ee\u3002", "cache_lookup \u6309\u626b\u63cf\u7684\u5f53\u524d\u53c2\u6570\u67e5\u627e\u662f\u5426\u5df2\u7f13\u5b58\u5143\u7ec4\u3002\u627e\u5230\u73b0\u6709\u6761\u76ee\u65f6\uff0c\u5c06\u5176\u79fb\u5230 LRU \u5217\u8868\u672b\u5c3e\uff0c\u5c06 *found \u8bbe\u4e3a true \u5e76\u8fd4\u56de\u3002\u627e\u4e0d\u5230\u65f6\uff0c\u65b0\u5efa\u6761\u76ee\u5e76\u52a0\u5165\u5217\u8868\u672b\u5c3e\uff0c\u540c\u65f6\u66f4\u65b0\u7f13\u5b58\u5185\u5b58\u8bb0\u8d26\uff1b\u5982\u679c\u8d85\u51fa\u9884\u7b97\u5219\u79fb\u9664\u8f83\u65e7\u6761\u76ee\u3002\u6210\u529f\u91ca\u653e\u8db3\u591f\u5185\u5b58\u65f6\u8fd4\u56de\u65b0\u6761\u76ee\uff0c\u5426\u5219\u8fd4\u56de NULL\u3002", "\u5982\u679c\u8d85\u51fa\u5185\u5b58\u9884\u7b97\uff0c\u5c31\u91ca\u653e\u90e8\u5206\u7f13\u5b58\u7a7a\u95f4\u3002"]}, {"title": "\u5e76\u884c\u6267\u884c\u4e0e\u8fd0\u884c\u4fe1\u606f\u91c7\u96c6", "paragraphs": ["\u4ee5\u4e0b\u6e90\u7801\u56de\u8c03\u53ef\u4ee5\u534f\u8c03\u6267\u884c\u6216\u6536\u96c6\u5de5\u4f5c\u8fdb\u7a0b\u7684\u6d4b\u91cf\u6570\u636e\u3002\u56de\u8c03\u5b58\u5728\u4e0d\u4ee3\u8868\u8be5\u8282\u70b9\u666e\u904d\u652f\u6301\u5171\u4eab\u5e76\u884c\u626b\u63cf\u6216\u5171\u4eab\u72b6\u6001\u3002", "\u6b64\u6784\u5efa\u7684\u56de\u8c03\uff1aExecMemoizeEstimate, ExecMemoizeInitializeDSM, ExecMemoizeInitializeWorker, ExecMemoizeRetrieveInstrumentation."]}, {"title": "\u6267\u884c\u5668\u5b9e\u73b0\u8bf4\u660e", "paragraphs": ["nodeMemoize.c\uff1a\u7f13\u5b58\u53c2\u6570\u5316\u8282\u70b9\u7ed3\u679c\u7684\u4f8b\u7a0b\u3002", "Memoize \u8282\u70b9\u4f4d\u4e8e\u8ba1\u5212\u6811\u4e2d\u7684\u53c2\u6570\u5316\u8282\u70b9\u4e4b\u4e0a\uff0c\u7528\u6765\u7f13\u5b58\u5176\u7ed3\u679c\u3002\u4f7f\u7528\u5df2\u7ecf\u51fa\u73b0\u8fc7\u7684\u53c2\u6570\u503c\u518d\u6b21\u626b\u63cf\u65f6\uff0c\u53ef\u4ee5\u76f4\u63a5\u4ece\u7f13\u5b58\u8bfb\u53d6\u5143\u7ec4\uff0c\u65e0\u9700\u91cd\u65b0\u626b\u63cf\u5185\u90e8\u8282\u70b9\u3002\u89c4\u5212\u5668\u8ba4\u4e3a\u91cd\u590d\u4f7f\u7528\u5df2\u6709\u53c2\u6570\u503c\u7684\u6982\u7387\u8db3\u4ee5\u62b5\u507f\u989d\u5916\u8282\u70b9\u5f00\u9500\u65f6\uff0c\u53ef\u80fd\u9009\u62e9\u8fd9\u79cd\u8282\u70b9\u3002", "\u7f13\u5b58\u91c7\u7528\u54c8\u5e0c\u8868\u3002\u7f13\u5b58\u7528\u6ee1\u65f6\u4e0d\u4f1a\u5c06\u5143\u7ec4\u5199\u5165\u78c1\u76d8\uff0c\u800c\u662f\u6dd8\u6c70\u6700\u8fd1\u6700\u5c11\u4f7f\u7528\u7684\u7f13\u5b58\u6761\u76ee\u3002\u901a\u8fc7\u628a\u65b0\u6761\u76ee\u548c\u6bcf\u6b21\u67e5\u627e\u7684\u6761\u76ee\u79fb\u5230\u53cc\u5411\u94fe\u8868\u672b\u5c3e\u6765\u8bb0\u5f55\u4f7f\u7528\u6b21\u5e8f\uff0c\u4f7f\u8f83\u4e45\u672a\u7528\u7684\u6761\u76ee\u9010\u6e10\u79fb\u5230 LRU \u5217\u8868\u5934\u90e8\u3002", "\u8c03\u7528\u8005\u6709\u65f6\u4e0d\u4f1a\u5b8c\u6210\u5168\u90e8\u626b\u63cf\u3002\u4f8b\u5982\uff0c\u534a\u8fde\u63a5\u627e\u5230\u4e00\u4e2a\u5339\u914d\u5143\u7ec4\u540e\u5c31\u8df3\u5230\u4e0b\u4e00\u4e2a\u5916\u4fa7\u5143\u7ec4\uff0c\u4e0d\u518d\u7ee7\u7eed\u6b64\u6b21\u5185\u4fa7\u626b\u63cf\u3002\u56e0\u6b64\u5fc5\u987b\u8ddf\u8e2a\u7f13\u5b58\u6761\u76ee\u662f\u5426\u5b8c\u6574\uff1b\u901a\u5e38\u626b\u63cf\u8bfb\u4e0d\u5230\u66f4\u591a\u5143\u7ec4\u65f6\u624d\u53ef\u786e\u8ba4\u5b8c\u6574\u3002\u4e0d\u8fc7\u67d0\u4e9b\u60c5\u51b5\u4e0b\u4e0d\u5fc5\u8017\u5c3d\u626b\u63cf\u4fbf\u80fd\u6807\u8bb0\u5b8c\u6574\u3002\u4f8b\u5982\u552f\u4e00\u8fde\u63a5\u5df2\u77e5\u6bcf\u4e2a\u5916\u4fa7\u5143\u7ec4\u6700\u591a\u5339\u914d\u4e00\u884c\u3002\u4e3a\u652f\u6301\u8fd9\u79cd\u60c5\u51b5\uff0c\u7f13\u5b58\u5141\u8bb8\u8bbe\u7f6e singlerow \u9009\u9879\uff0c\u5728\u4ece\u5b50\u8282\u70b9\u8bfb\u53d6\u7b2c\u4e00\u4e2a\u5143\u7ec4\u540e\u5373\u6807\u8bb0\u8be5\u7f13\u5b58\u6761\u76ee\u5b8c\u6574\u3002", "\u4e3a\u4e00\u7ec4\u53c2\u6570\u586b\u5145\u7f13\u5b58\u65f6\uff0c\u53ef\u80fd\u65e0\u6cd5\u91ca\u653e\u8db3\u591f\u7684\u5185\u5b58\u6765\u5b58\u50a8\u66f4\u591a\u5143\u7ec4\u3002\u6b64\u65f6\u5176\u4ed6\u7f13\u5b58\u6761\u76ee\u5df2\u7ecf\u5168\u90e8\u88ab\u6dd8\u6c70\u3002\u5982\u679c\u518d\u7f13\u5b58\u4e00\u4e2a\u5143\u7ec4\u4f1a\u8d85\u51fa\u9884\u7b97\uff0c\u5fc5\u987b\u91ca\u653e\u6b63\u5728\u586b\u5145\u7684\u6761\u76ee\uff0c\u5e76\u5c06\u72b6\u6001\u673a\u5207\u6362\u4e3a MEMO_CACHE_BYPASS_MODE\u3002\u8fd9\u610f\u5473\u7740\u672c\u6b21\u626b\u63cf\u4e0d\u518d\u5c1d\u8bd5\u7f13\u5b58\u540e\u7eed\u5143\u7ec4\uff0c\u56e0\u4e3a\u5185\u5b58\u4e0d\u8db3\u3002\u4e0b\u6b21\u91cd\u65b0\u626b\u63cf\u65f6\u72b6\u6001\u673a\u4f1a\u91cd\u7f6e\uff1b\u5982\u679c\u4e0b\u4e00\u7ec4\u53c2\u6570\u7684\u5143\u7ec4\u7f13\u5b58\u9700\u8981\u8f83\u5c11\u5185\u5b58\uff0c\u5c31\u53ef\u80fd\u91cd\u65b0\u5f00\u59cb\u7f13\u5b58\u6709\u7528\u7684\u6761\u76ee\u3002"]}, {"code": "case T_Memoize:\n\t\t\tpname = sname = \"Memoize\";\n\t\t\tbreak;", "title": "\u6838\u5fc3\u6e90\u7801\u4e2d\u7684 EXPLAIN \u6807\u8bc6"}], "strategies": [], "description": ["\u7f13\u5b58\u53c2\u6570\u5316\u5b50\u8ba1\u5212\u7684\u7ed3\u679c\uff0c\u5e76\u5728\u76f8\u540c\u53c2\u6570\u503c\u518d\u6b21\u51fa\u73b0\u65f6\u590d\u7528\u3002"], "localization": {"status": "complete", "sources": [], "language": "zh", "original_text": {"/versions/18/description/0": "Caches results from a parameterized child and reuses them when the same parameter values recur.", "/versions/18/facts/0/label": "Core node tag", "/versions/18/facts/1/label": "Structured EXPLAIN Node Type", "/versions/18/facts/2/label": "Inputs", "/versions/18/facts/2/value": "One parameterized child plan", "/versions/18/facts/3/label": "Output", "/versions/18/facts/3/value": "Cached or newly produced child tuples", "/versions/18/facts/4/label": "Executor initializer", "/versions/18/facts/5/label": "Memory mechanism", "/versions/18/tables/0/title": "EXPLAIN labels in this source build", "/versions/18/related/1/label": "Using EXPLAIN", "/versions/18/related/2/label": "Parallel plans", "/versions/18/sections/0/title": "EXPLAIN names and attributes", "/versions/18/sections/1/title": "Memory and temporary storage", "/versions/18/sections/2/title": "Parallel execution and instrumentation", "/versions/18/sections/3/title": "Executor implementation notes", "/versions/18/sections/4/title": "EXPLAIN identity in core source", "/versions/18/memory/description": "The Memoize cache evicts least-recently-used entries instead of spilling cached tuples to disk. If a result cannot fit, caching can enter bypass mode for that scan.", "/versions/18/sections/0/paragraphs/0": "Structured formats use the Node Type above. Text-format spellings can also include operation, strategy, join type, scan direction or aggregation-stage attributes.", "/versions/18/sections/0/paragraphs/1": "Text names recorded by this source: Memoize.", "/versions/18/sections/0/paragraphs/2": "Parallel-aware and parallel-safe are different plan properties. A node running inside a parallel worker is not necessarily a parallel-aware node.", "/versions/18/sections/1/paragraphs/0": "The Memoize cache evicts least-recently-used entries instead of spilling cached tuples to disk. If a result cannot fit, caching can enter bypass mode for that scan.", "/versions/18/sections/1/paragraphs/1": "The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.", "/versions/18/sections/1/paragraphs/2": "It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.", "/versions/18/sections/1/paragraphs/3": "cache_lookup Perform a lookup to see if we've already cached tuples based on the scan's current parameters. If we find an existing entry we move it to the end of the LRU list, set *found to true then return it. If we don't find an entry then we create a new one and add it to the end of the LRU list. We also update cache memory accounting and remove older entries if we go over the memory budget. If we managed to free enough memory we return the new entry, else we return NULL.", "/versions/18/sections/1/paragraphs/4": "If we've gone over our memory budget, then we'll free up some space in the cache.", "/versions/18/sections/2/paragraphs/0": "The source callbacks below can coordinate execution or collect worker instrumentation. Their presence is not a blanket claim that this node supports a shared parallel scan or shared state.", "/versions/18/sections/2/paragraphs/1": "Callbacks in this build: ExecMemoizeEstimate, ExecMemoizeInitializeDSM, ExecMemoizeInitializeWorker, ExecMemoizeRetrieveInstrumentation.", "/versions/18/sections/3/paragraphs/0": "nodeMemoize.c Routines to handle caching of results from parameterized nodes", "/versions/18/sections/3/paragraphs/1": "Memoize nodes are intended to sit above parameterized nodes in the plan tree in order to cache results from them. The intention here is that a repeat scan with a parameter value that has already been seen by the node can fetch tuples from the cache rather than having to re-scan the inner node all over again. The query planner may choose to make use of one of these when it thinks rescans for previously seen values are likely enough to warrant adding the additional node.", "/versions/18/sections/3/paragraphs/2": "The method of cache we use is a hash table. When the cache fills, we never spill tuples to disk, instead, we choose to evict the least recently used cache entry from the cache. We remember the least recently used entry by always pushing new entries and entries we look for onto the tail of a doubly linked list. This means that older items always bubble to the top of this LRU list.", "/versions/18/sections/3/paragraphs/3": "Sometimes our callers won't run their scans to completion. For example a semi-join only needs to run until it finds a matching tuple, and once it does, the join operator skips to the next outer tuple and does not execute the inner side again on that scan. Because of this, we must keep track of when a cache entry is complete, and by default, we know it is when we run out of tuples to read during the scan. However, there are cases where we can mark the cache entry as complete without exhausting the scan of all tuples. One case is unique joins, where the join operator knows that there will only be at most one match for any given outer tuple. In order to support such cases we allow the \"singlerow\" option to be set for the cache. This option marks the cache entry as complete after we read the first tuple from the subnode.", "/versions/18/sections/3/paragraphs/4": "It's possible when we're filling the cache for a given set of parameters that we're unable to free enough memory to store any more tuples. If this happens then we'll have already evicted all other cache entries. When caching another tuple would cause us to exceed our memory budget, we must free the entry that we're currently populating and move the state machine into MEMO_CACHE_BYPASS_MODE. This means that we'll not attempt to cache any further tuples for this particular scan. We don't have the memory for it. The state machine will be reset again on the next rescan. If the memory requirements to cache the next parameter's tuples are less demanding, then that may allow us to start putting useful entries back into the cache again.", "/versions/18/tables/0/columns/0/label": "Text-format label", "/versions/18/tables/0/columns/1/label": "Structured node identity"}, "fallback_fields": [], "source_language": "en", "original_snapshot_sha256": "b731cc977a455651e09a53bbac518e5cb75d41abf449a3b6b6c7c0f4a6dc400e"}, "evidence_kind": "source and documentation", "explain_names": ["Memoize"], "partial_modes": [], "comparison_data": {"node_tag": "T_Memoize", "strategies": [], "text_names": ["Memoize"], "initializer": "ExecInitMemoize", "partial_modes": [], "memory_mechanism": "eviction", "parallel_callbacks": ["ExecMemoizeEstimate", "ExecMemoizeInitializeDSM", "ExecMemoizeInitializeWorker", "ExecMemoizeRetrieveInstrumentation"]}, "comparison_hash": "ce79083064802c6772e741ce60e19e9c932b05d1f1a5ed00e8f8f2e86d678f5b", "explain_prefixes": ["Parallel", "Async"], "runtime_verified": false, "source_inventory": {"explain": "src/backend/commands/explain.c", "executor": "src/backend/executor/execProcnode.c", "implementation": "src/backend/executor/nodeMemoize.c"}, "parallel_callbacks": ["ExecMemoizeEstimate", "ExecMemoizeInitializeDSM", "ExecMemoizeInitializeWorker", "ExecMemoizeRetrieveInstrumentation"]}, "comparison": {"left": "13", "right": "14", "status": "added", "diff": "--- PostgreSQL 13\n+++ PostgreSQL 14\n@@ -1 +1,16 @@\n-\u8be5\u7248\u672a\u6536\u5f55\n+{\n+  \"initializer\": \"ExecInitMemoize\",\n+  \"memory_mechanism\": \"eviction\",\n+  \"node_tag\": \"T_Memoize\",\n+  \"parallel_callbacks\": [\n+    \"ExecMemoizeEstimate\",\n+    \"ExecMemoizeInitializeDSM\",\n+    \"ExecMemoizeInitializeWorker\",\n+    \"ExecMemoizeRetrieveInstrumentation\"\n+  ],\n+  \"partial_modes\": [],\n+  \"strategies\": [],\n+  \"text_names\": [\n+    \"Memoize\"\n+  ]\n+}"}}