优化器刺客之limit 1--Order by col limit n 代价预估优化探索

一、现象

order by 排序加了limit后更慢了?

test=# explain analyze  select userid from dba_users where  username like '%aaaaaaaaaaaaaaaaaa%' order by userid ;QUERY PLAN                                                                  
----------------------------------------------------------------------------------------------------------------------------------------------Sort  (cost=2327.46..2328.96 rows=600 width=4) (actual time=109.316..109.318 rows=0 loops=1)Sort Key: useridSort Method: quicksort  Memory: 25kB->  Bitmap Heap Scan on dba_users  (cost=61.47..2299.78 rows=600 width=4) (actual time=109.311..109.312 rows=0 loops=1)Recheck Cond: ((username)::text ~~ '%aaaaaaaaaaaaaaaaaa%'::text)Rows Removed by Index Recheck: 40904Heap Blocks: exact=31502->  Bitmap Index Scan on dba_users_username_idx  (cost=0.00..61.32 rows=600 width=0) (actual time=22.520..22.520 rows=40904 loops=1)Index Cond: ((username)::text ~~ '%aaaaaaaaaaaaaaaaaa%'::text)Planning Time: 0.149 msExecution Time: 109.350 ms
(11 rows)test=# 
test=# explain analyze  select userid from dba_users where  username like '%aaaaaaaaaaaaaaaaaa%'    order by userid   limit 1 ;                                                                                                         QUERY PLAN                                                          
---------------------------------------------------------------------------------------------------------------------------------------------Limit  (cost=0.43..408.59 rows=1 width=4) (actual time=3558.960..3558.961 rows=0 loops=1)->  Index Scan using dba_users_pkey on dba_users  (cost=0.43..244895.74 rows=600 width=4) (actual time=3558.958..3558.959 rows=0 loops=1)Filter: ((username)::text ~~ '%aaaaaaaaaaaaaaaaaa%'::text)Rows Removed by Filter: 6000000Planning Time: 0.171 msExecution Time: 3558.983 ms
(6 rows)test=#

dba_users有600w条数据,username符合检索条件的0行, order by userid limit 1 耗时3558.983 ms,order by userid 耗时109.350 ms。
limit 1是匹配到第一条数据后就返回,这里却更慢了? 看起来不符合预期。
表结构如下:

test=# \d+ dba_usersTable "public.dba_users"Column  |         Type          | Collation | Nullable | Default | Storage  | Compression | Stats target | Description 
----------+-----------------------+-----------+----------+---------+----------+-------------+--------------+-------------userid   | integer               |           | not null |         | plain    |             |              | username | character varying(64) |           |          |         | extended |             |              | password | character varying(64) |           |          |         | extended |             |              | 
Indexes:"dba_users_pkey" PRIMARY KEY, btree (userid)"dba_users_password_idx" btree (password)"dba_users_username_idx" gin (username gin_trgm_ops)
Access method: heaptest=#通常我们可以给order by字段做运算或者类型转换来矫正优化器走实际更优的执行计划。
test=# explain analyze select userid from dba_users where  username like '%aaaaaaaaaaaaaaaaaa%' order by userid + 0 limit 1;QUERY PLAN                                                                     
----------------------------------------------------------------------------------------------------------------------------------------------------Limit  (cost=2302.78..2304.28 rows=1 width=4) (actual time=109.117..109.119 rows=0 loops=1)->  Sort  (cost=2302.78..2304.28 rows=600 width=4) (actual time=109.116..109.117 rows=0 loops=1)Sort Key: ((userid + 0))Sort Method: quicksort  Memory: 25kB->  Bitmap Heap Scan on dba_users  (cost=61.47..2299.78 rows=600 width=4) (actual time=109.110..109.111 rows=0 loops=1)Recheck Cond: ((username)::text ~~ '%aaaaaaaaaaaaaaaaaa%'::text)Rows Removed by Index Recheck: 40904Heap Blocks: exact=31502->  Bitmap Index Scan on dba_users_username_idx  (cost=0.00..61.32 rows=600 width=0) (actual time=20.856..20.856 rows=40904 loops=1)Index Cond: ((username)::text ~~ '%aaaaaaaaaaaaaaaaaa%'::text)Planning Time: 0.156 msExecution Time: 109.149 ms
(12 rows)test=#

或者order by字段和where字段建个组合索引? 多列统计信息?这些都是一些规避的方法,优化器自身为什么选择了不优的计划?

二、分析

这个问题一直被诟病,不少文章分析过很多场景,统计信息不准?索引损坏?数据分布问题?我们一起深入探索其中的奥秘。
PostgreSQL的优化器是自底向上生成执行计划,当查询小于12个表,使用的是动态规划算法,在每个计划节点执行各种可能的path和检索方法,然后计算出最小代价path作为最优解,也就是最终的执行计划。

DEBUG跟踪这个过程。
limit 的cost计算是在adjust_limit_rows_costs函数里进行的,根据上一节点subpath的startup_cost和total_cost计算出limit count后对应的cost

如下subpath是T_IndexPath, startup_cost=0.4325 total_cost=244895.745
limit 1对应的startup_cost=0.4325 total_cost= startup_cost + (input_total_cost - input_startup_cost) count_rows / input_rows;
= 0.4325 + (244895.745 - 0.4325 ) * 1/600
= 408.5913541666666667
这个对应的是order by userid limit 1执行计划的cost:Limit (cost=0.43…408.59 rows=1 width=4)

在set_cheapest里比较表扫描的最优方式,循环比较pathlist的每个节点的startup_cost和total_cost,
以下最优total_cost是T_BitmapHeapPath,最优startup_cost是T_seqScan

最终set_cheapest函数里一直角逐出Limit节点最优的startup_cost是0.4325,total_cost是408.59135416666669,这个对应到了order by userid Limit 1 这个plan的total_cost:Limit (cost=0.43…408.59 rows=1 width=4)

在get_cheapest_fractional_path函数里返回best_path 也就是以上total_cost=408.59135416666669的path,即order by userid Limit 1的plan。
并以此创建执行计划,最终执行器执行。

那么从整个过程来看,我们需要关注的是整个计划最终节点的startup_cost和total_cost,优化器会选择最优total_cost的path作为best_path。
代价预估order by userid limit 1时,total_cost为:408.59(实际执行的total_time为:3558.961 ms)
预估order by userid + 0 limit 1时,total_cost为:2304.28(实际执行的total_time为:109.119 ms)
显而易见优化器选择了total_cost更小的408.59所在的path作为执行计划。
很明显代价预估有问题,我们开始就跟踪了order by userid limit 1的cost计算,计算的数值本身没有问题。
是优化器的缺陷?再把执行计划拉出来遛遛。

test=# explain analyze  select userid from dba_users where  username like '%aaaaaaaaaaaaaaaaaa%'    order by userid   limit 1 ;                                                                                                         QUERY PLAN                                                          
---------------------------------------------------------------------------------------------------------------------------------------------Limit  (cost=0.43..408.59 rows=1 width=4) (actual time=3558.960..3558.961 rows=0 loops=1)->  Index Scan using dba_users_pkey on dba_users  (cost=0.43..244895.74 rows=600 width=4) (actual time=3558.958..3558.959 rows=0 loops=1)Filter: ((username)::text ~~ '%aaaaaaaaaaaaaaaaaa%'::text)Rows Removed by Filter: 6000000Planning Time: 0.171 msExecution Time: 3558.983 ms
(6 rows)test=#

cost我们之前已经计算过了,但是从actual time来看,代价预估偏差还是比较大的,
cost:Index Scan startup_cost=0.43 total_cost=244895.74 , limit startup_cost=0.43 total_cost=408.59
从预估来看因为没有offset 所以startup_cost都是0.43,根据公式计算的 limit 的total_cost=408.59,比244895.74小了很多。

actual: Index Scan startup_time=3558.958 total_time=3558.959,limit startup_time=3558.960 total_time=3558.961
从实际执行来看Index startup_time=3558.958 total_time=3558.959,索引扫描startup_time启动代价3558.958ms?

startup_time可以理解为扫描到第一条数据的时间,这里可以虽然走了userid的pkey索引,但是根据username like ‘%aaaaaaaaaaaaaaaaaa%’ filter了600w行,就是说回表匹配了一遍所有行,因此耗时主要是在这里。

再看下limit 节点total_cost计算代价公式

*total_cost=startup_cost + (input_total_cost - input_startup_cost) count_rows / input_rows
总代价 = 父节点启动代价 + (总代价 - 启动代价)即父节点运行代价 * limit 行数/ 预估输出的总行数

这里在where 条件不含排序字段走排序字段索引情况下,有可能通过索引匹配到第一条符合条件的数据会比较久,就是说要考虑索引扫描的整体代价作为limit的启动代价(当前默认逻辑是通过索引很快找到第一条数据,然后输出limit n行,因此整体cost在这个场景下是偏小的),最极端的场景很可能是先扫描了整个索引并且回表去匹配数据,这个cost要预估进去。

因此这里的startup_cost 需要替换为total_cost, 计算公式可以调整为:
*total_cost=total_cost + (input_total_cost - input_startup_cost) count_rows / input_rows

三、方案

当where 条件不含order by字段走order by字段索引不进行sort的情况下,flag(limit_total_cost)会置为true,这个时候就走新的计算逻辑。

        if (count_est != 0){double          count_rows;if (count_est > 0) count_rows = (double) count_est;elsecount_rows = clamp_row_est(input_rows * 0.10);if (count_rows > *rows)count_rows = *rows;if (input_rows > 0){if (limit_total_cost){*total_cost = *total_cost + (input_total_cost - input_startup_cost)* count_rows / input_rows;}else*total_cost = *startup_cost +(input_total_cost - input_startup_cost)* count_rows / input_rows;}*rows = count_rows;if (*rows < 1)*rows = 1;}
}

四、验证

执行计划显示order by userid limit 1和之前order by userid +0 limit 1的执行计划相同,sql耗时符合预期。

test=# explain analyze  select userid from dba_users where  username like '%aaaaaaaaaaaaaaaaaa%' order by userid limit 1 ;QUERY PLAN                                                                     
----------------------------------------------------------------------------------------------------------------------------------------------------Limit  (cost=2302.78..2304.28 rows=1 width=4) (actual time=140.581..140.585 rows=0 loops=1)->  Sort  (cost=2302.78..2304.28 rows=600 width=4) (actual time=140.576..140.579 rows=0 loops=1)Sort Key: useridSort Method: quicksort  Memory: 25kB->  Bitmap Heap Scan on dba_users  (cost=61.47..2299.78 rows=600 width=4) (actual time=140.534..140.536 rows=0 loops=1)Recheck Cond: ((username)::text ~~ '%aaaaaaaaaaaaaaaaaa%'::text)Rows Removed by Index Recheck: 40904Heap Blocks: exact=31502->  Bitmap Index Scan on dba_users_username_idx  (cost=0.00..61.32 rows=600 width=0) (actual time=22.800..22.802 rows=40904 loops=1)Index Cond: ((username)::text ~~ '%aaaaaaaaaaaaaaaaaa%'::text)Planning Time: 1.413 msExecution Time: 141.032 ms
(12 rows)test=#

再验证一个稍微复杂一点的场景。
修改前:

test=# explain analyze  select userid from dba_users where  username like '%aaaaaaaaaaaaaaaaaa%'   and userid in (select city_id from measurement where logdate > '2023-02-01' and logdate < '2023-05-01' and name like '%Nickyoung%' order by city_id   limit 5) ;QUERY PLAN                                                                                                
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Nested Loop  (cost=2143.01..2185.00 rows=1 width=4) (actual time=8153.711..8153.717 rows=0 loops=1)->  HashAggregate  (cost=2142.57..2142.62 rows=5 width=4) (actual time=8153.710..8153.713 rows=0 loops=1)Group Key: measurement.city_idBatches: 1  Memory Usage: 24kB->  Limit  (cost=1.32..2142.56 rows=5 width=4) (actual time=8153.707..8153.710 rows=0 loops=1)->  Merge Append  (cost=1.32..582419.24 rows=1360 width=4) (actual time=8153.705..8153.708 rows=0 loops=1)Sort Key: measurement.city_id->  Index Scan using measurement_y2023m02_city_id_idx on measurement_y2023m02 measurement_1  (cost=0.43..59989.39 rows=140 width=4) (actual time=855.005..855.005 rows=0 loops=1)Filter: ((logdate > '2023-02-01'::date) AND (logdate < '2023-05-01'::date) AND ((name)::text ~~ '%Nickyoung%'::text))Rows Removed by Filter: 1399554->  Index Scan using measurement_y2023m03_city_id_idx on measurement_y2023m03 measurement_2  (cost=0.43..265374.77 rows=620 width=4) (actual time=3747.483..3747.483 rows=0 loops=1)Filter: ((logdate > '2023-02-01'::date) AND (logdate < '2023-05-01'::date) AND ((name)::text ~~ '%Nickyoung%'::text))Rows Removed by Filter: 6197829->  Index Scan using measurement_y2023m04_city_id_idx on measurement_y2023m04 measurement_3  (cost=0.43..257037.48 rows=600 width=4) (actual time=3551.214..3551.214 rows=0 loops=1)Filter: ((logdate > '2023-02-01'::date) AND (logdate < '2023-05-01'::date) AND ((name)::text ~~ '%Nickyoung%'::text))Rows Removed by Filter: 6001729->  Memoize  (cost=0.44..8.46 rows=1 width=4) (never executed)Cache Key: measurement.city_idCache Mode: logical->  Index Scan using dba_users_pkey on dba_users  (cost=0.43..8.45 rows=1 width=4) (never executed)Index Cond: (userid = measurement.city_id)Filter: ((username)::text ~~ '%aaaaaaaaaaaaaaaaaa%'::text)Planning Time: 3.282 msExecution Time: 8153.877 ms
(24 rows)test=#

修改后:

test=# explain analyze  select userid from dba_users where  username like '%aaaaaaaaaaaaaaaaaa%'   and userid in (select city_id from measurement where logdate > '2023-02-01' and logdate < '2023-05-01' and name like '%Nickyoung%' order by city_id   limit 5) ;QUERY PLAN                                                                                
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------Nested Loop  (cost=5982.58..6024.57 rows=1 width=4) (actual time=0.129..0.131 rows=0 loops=1)->  HashAggregate  (cost=5982.13..5982.18 rows=5 width=4) (actual time=0.128..0.130 rows=0 loops=1)Group Key: measurement.city_idBatches: 1  Memory Usage: 24kB->  Limit  (cost=5982.11..5982.12 rows=5 width=4) (actual time=0.126..0.127 rows=0 loops=1)->  Sort  (cost=5982.11..5985.51 rows=1360 width=4) (actual time=0.124..0.126 rows=0 loops=1)Sort Key: measurement.city_idSort Method: quicksort  Memory: 25kB->  Append  (cost=177.08..5959.52 rows=1360 width=4) (actual time=0.109..0.110 rows=0 loops=1)->  Bitmap Heap Scan on measurement_y2023m02 measurement_1  (cost=177.08..696.08 rows=140 width=4) (actual time=0.041..0.041 rows=0 loops=1)Recheck Cond: ((name)::text ~~ '%Nickyoung%'::text)Filter: ((logdate > '2023-02-01'::date) AND (logdate < '2023-05-01'::date))->  Bitmap Index Scan on measurement_y2023m02_name_idx  (cost=0.00..177.05 rows=140 width=0) (actual time=0.039..0.039 rows=0 loops=1)Index Cond: ((name)::text ~~ '%Nickyoung%'::text)->  Bitmap Heap Scan on measurement_y2023m03 measurement_2  (cost=377.60..2665.41 rows=620 width=4) (actual time=0.031..0.031 rows=0 loops=1)Recheck Cond: ((name)::text ~~ '%Nickyoung%'::text)Filter: ((logdate > '2023-02-01'::date) AND (logdate < '2023-05-01'::date))->  Bitmap Index Scan on measurement_y2023m03_name_idx  (cost=0.00..377.45 rows=620 width=0) (actual time=0.031..0.031 rows=0 loops=1)Index Cond: ((name)::text ~~ '%Nickyoung%'::text)->  Bitmap Heap Scan on measurement_y2023m04 measurement_3  (cost=377.50..2591.23 rows=600 width=4) (actual time=0.036..0.036 rows=0 loops=1)Recheck Cond: ((name)::text ~~ '%Nickyoung%'::text)Filter: ((logdate > '2023-02-01'::date) AND (logdate < '2023-05-01'::date))->  Bitmap Index Scan on measurement_y2023m04_name_idx  (cost=0.00..377.35 rows=600 width=0) (actual time=0.036..0.036 rows=0 loops=1)Index Cond: ((name)::text ~~ '%Nickyoung%'::text)->  Memoize  (cost=0.44..8.46 rows=1 width=4) (never executed)Cache Key: measurement.city_idCache Mode: logical->  Index Scan using dba_users_pkey on dba_users  (cost=0.43..8.45 rows=1 width=4) (never executed)Index Cond: (userid = measurement.city_id)Filter: ((username)::text ~~ '%aaaaaaaaaaaaaaaaaa%'::text)Planning Time: 2.047 msExecution Time: 0.254 ms
(32 rows)test=#

五、小结

也算牛刀小试修正了下limit的cost预估。不过这种修改方式看起来是不优雅不专业的,内核中可能有很多特例,我们不可能只是一股脑堆if else switch case逻辑。就像Linus说的,排除特例完美覆盖所有情况才是好的代码。

针对这个case,我认为优化器目前limit节点的cost计算逻辑需要加强,计算公式可能需要更复杂的关系因子,或者使用更合理的数学表达式。鄙人不才,还需持续学习积累。

代价预估在一些特定场景下难免会有偏差,其中统计信息不准导致的场景可能会多一些,可以参考这篇了解下统计信息的原理<深入浅出统计信息内核原理(上):Compressed Histogram>。

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

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

相关文章

ElementUI Form:Radio 单选框

ElementUI安装与使用指南 Radio 单选框 点击下载learnelementuispringboot项目源码 效果图 el-radio.vue 页面效果图 项目里el-radio.vue代码 <script> export default {name: el_radio,data() {return {radio: 1,radio2: 2,radio3: 3,radio4: 上海,radio5: 上海,ra…

12个适合后端程序员的前端框架

前言 今天我们分享12个适合后端程序员的前端框架&#xff0c;本文中的所有前端框架都已经收录到适合后端程序员的前端框架GitHub Issues知识库中&#xff0c;假如大家有更好前端框架推荐欢迎到以下GitHub项目地址留言或者在文末留言&#xff08;注意&#xff1a;排名不分先后&a…

c#鼠标绘制

有用的没用的&#xff0c;用的上的用不上的&#xff0c;能写的不能写的&#xff0c;反正想起来就写了&#xff0c;比如这篇&#xff0c;好像一般也没什么用&#xff0c;emmm&#xff0c;或许&#xff0c;做录制软件的时候可以用一下。 顾名思义&#xff0c;本篇主要就是来实现将…

dvwa,xss反射型lowmedium

xss&#xff0c;反射型&#xff0c;low&&medium low发现xss本地搭建实操 medium作为初学者的我第一次接触比较浅的绕过思路high low 发现xss 本关无过滤 <script>alert(/xss/)</script> //或 <script>confirm(/xss/)</script> //或 <scr…

幻兽帕鲁服务器游戏怎么升级版本?

幻兽帕鲁服务器游戏怎么升级版本&#xff1f;自建幻兽帕鲁服务器进入Palworld游戏提示“您正尝试加入的比赛正在运行不兼容的游戏版本&#xff0c;请尝试升级游戏版本”什么原因&#xff1f;这是由于你的客户端和幻兽帕鲁服务器版本不匹配&#xff0c;如何解决&#xff1f;更新…

故障诊断 | 一文解决,SVM支持向量机的故障诊断(Matlab)

效果一览 文章概述 故障诊断 | 一文解决,SVM支持向量机的故障诊断(Matlab) 支持向量机(Support Vector Machine,SVM)是一种常用的监督学习算法,用于分类和回归分析。SVM的主要目标是找到一个最优的超平面(或者在非线性情况下是一个最优的超曲面),将不同类别的样本分开…

Flutter的安装与环境配置

一、下载安装Futter&#xff1a; 1、Flutter中文文档&#xff1a; 安装和环境配置 - Flutter 中文文档 - Flutter 中文开发者网站 - Flutter 2、下载 Futter SDK&#xff1a; Flutter中文文档 里面有&#xff0c;下载完成之后找个文件夹解压出来&#xff0c;最好不要将 Flu…

大数据分析|从七个特征理解大数据分析

文献来源&#xff1a;Saggi M K, Jain S. A survey towards an integration of big data analytics to big insights for value-creation[J]. Information Processing & Management, 2018, 54(5): 758-790. 下载链接&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1…

qiankun子应用静态资源404问题有效解决(遇到了http请求静态文件404、css文件引用图片无法显示、svg图片转换成 base64无法显示等问题)

在&#x1f449;&#x1f3fb; qiankun微前端部署&#x1f448;&#x1f3fb;这个部署方式的前提下&#xff0c;遇到的问题并解决问题的过程 >> 问题现象 通过http请求本地的静态json文件404 css中部分引入的图片无法显示 >> 最开始的解决方式 在&#x1f449;…

微信小程序(二十五)条件判断语句与结构隐藏

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.条件判断语句的演示 2.隐藏结构的演示 源码&#xff1a; index.wxml <view><!-- wx:if和wx:else为条件判断语句 --><text wx:if"{{isLogin}}">已登入的用户</text><tex…

【HarmonyOS应用开发】ArkUI 开发框架-基础篇-第一部分(七)

常用基础组件 一、组件介绍 组件&#xff08;Component&#xff09;是界面搭建与显示的最小单位&#xff0c;HarmonyOS ArkUI声明式开发范式为开发者提供了丰富多样的UI组件&#xff0c;我们可以使用这些组件轻松的编写出更加丰富、漂亮的界面。组件根据功能可以分为以下五大类…

Flink中StateBackend(工作状态)与Checkpoint(状态快照)的关系

State Backends 由 Flink 管理的 keyed state 是一种分片的键/值存储&#xff0c;每个 keyed state 的工作副本都保存在负责该键的 taskmanager 本地中。另外&#xff0c;Operator state 也保存在机器节点本地。Flink 定期获取所有状态的快照&#xff0c;并将这些快照复制到持…

Android Studio 安装配置教程 - Windows版

Android Studio下载 安装&#xff1a; 下载&#xff1a; Android Studio Hedgehog | 2023.1.1 | Android Developers (google.cn) 安装&#xff1a; 基本不需要思考跟着走 默认下一步 默认下一步 自定义修改路径&#xff0c;下一步 默认下一步&#xff0c;不勾选 默认下一…

RHCE 综合项目-博客

目录 业务需求 一、准备工作 1、配置静态IP 2、修改主机名及hosts映射 3、开启防火墙 4、时间同步 5、配置免密ssh登录 二、环境搭建 1、Server-web端安装LAMP环境软件 2、Server-NFS-DNS端上传博客软件 3、Server-NFS-DNS端设置NFS共享 三、Server-web设置 1、挂…

【代码随想录-链表】反转链表

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学习,不断总结,共同进步,活到老学到老导航 檀越剑指大厂系列:全面总结 jav…

OpenCV 2 - 矩阵的掩膜操作

1知识点 1-1 CV_Assert(myImage.depth() == CV_8U); 确保输入图像是无符号字符类型,若该函数括号内的表达式为false,则会抛出一个错误。 1-2 Mat.ptr(int i = 0); 获取像素矩阵的指针,索引 i 表示第几行,从0开始计行数。 1-3 const uchar* current = mylmage.ptr(row); 获得…

day26 节点操作——查找节点

目录 DOM节点查找节点父节点查找子节点查找兄弟关系查找 DOM节点 DOM节点&#xff1a; DOM树里每一个内容都称之为节点 节点类型&#xff1a; 元素节点&#xff1a;所有的标签&#xff0c;比如body、div html是根节点属性节点&#xff1a;所有的属性&#xff0c;比如href、cla…

1.26布雷斯悖论(设计做减法,使效率更高,netlogo模拟),自组织映射神经网络SOM

布雷斯悖论 红色的是普通道路&#xff0c;车越多通行时间越长 假定条件是 均衡状态就是两条路的通行时间相同 纳什均衡并不一定是全局最优 纳什均衡的关键就是单个个体做出改变时&#xff0c;只会使自己的利益受到损失&#xff0c;而不会使其他人发生改变 在达到纳什平衡时&…

让MySQL和Redis数据保持一致的4种策略

1 前言 先阐明一下 MySQL 和 Redis 的关系&#xff1a;MySQL 是数据库&#xff0c;用来持久化数据&#xff0c;一定程度上保证数据的可靠性&#xff1b;Redis 是用来当缓存&#xff0c;用来提升数据访问的性能。 关于如何保证 MySQL 和 Redis 中的数据一致&#xff08;即缓存…