MYSQL查询优化器

2023-11-06

MYSQL 逻辑结构

MySQL 使用典型的客户端/服务器(Client/Server)结构, 体系结构大体可以分为三层:客户端、服务器层以及存储引擎层。其中,服务器层又包括了连接管理、查询缓存 、SQL 接口、解析器、优化器、缓冲与缓存以及各种管理工具与服务等。逻辑结构图如下所示:

mysql logical

具体来说,每个组件的作用如下:

  • 客户端,连接 MySQL 服务器的各种工具和应用程序。例如 mysql 命令行工具、mysqladmin 以及各种驱动程序等。

  • 连接管理,负责监听和管理客户端的连接以及线程处理等。每一个连接到 MySQL 服务器的请求都会被分配一个连接线程。连接线程负责与客户端的通信,接受客户端发送的命令并且返回服务器处理的结果。

  • 查询缓存 ,用于将执行过的 SELECT 语句和结果缓存在内存中。每次执行查询之前判断是否命中缓存,如果命中直接返回缓存的结果。缓存命中需要满足许多条件,SQL 语句完全相同,上下文环境相同等。实际上除非是只读应用,查询缓存的失效频率非常高,任何对表的修改都会导致缓存失效;因此,查询缓存在 MySQL 8.0 中已经被删除。

  • SQL 接口,接收客户端发送的各种 DML和 DDL 命令,并且返回用户查询的结果。另外还包括所有的内置函数(日期、时间、数学以及加密函数)和跨存储引擎的功能,例如存储过程、触发器、视图等。

  • 解析器,对 SQL 语句进行解析,例如语义和语法的分析和检查,以及对象访问权限检查等。

  • 优化器,利用数据库的统计信息决定 SQL 语句的最佳执行方式。使用索引还是全表扫描的方式访问单个表,多表连接的实现方式等。优化器是决定查询性能的关键组件,而数据库的统计信息是优化器判断的基础。

  • 缓存与缓冲,由一系列缓存组成的,例如数据缓存、索引缓存以及对象权限缓存等。对于已经访问过的磁盘数据,在缓冲区中进行缓存;下次访问时可以直接读取内存中的数据,从而减少磁盘 IO。

  • 存储引擎,存储引擎是对底层物理数据执行实际操作的组件,为服务器层提供各种操作数据的 API。MySQL 支持插件式的存储引擎,包括 InnoDB、MyISAM、Memory 等。

MYSQL执行流程

优化器

 MySQL 查询优化器又叫成本优化器,使用基于成本的优化方式(Cost-based Optimization),以 SQL 语句作为输入,利用内置的成本模型和数据字典信息以及存储引擎的统计信息决定使用哪些步骤执行查询语句。

 查询优化和地图导航的概念非常相似,我们通常只需要输入想要的结果(目的地),优化器负责找到最有效的实现方式(最佳路线)。需要注意的是,导航并不一定总是返回最快的路线,因为系统获得的交通数据并不可能是绝对准确的;与此类似,优化器也是基于特定模型、各种配置和统计信息进行选择,因此也不可能总是获得最佳执行方式。

注意:mysql的优化器是基于查询成本的优化,不是基于查询时间的优化。

从高层次来说,MySQL Server 可以分为两部分:服务器层以及存储引擎层。其中,优化器工作在服务器层,位于存储引擎 API 之上。优化器的工作过程从语义上可以分为三个阶段:

  1. 逻辑转换,包括否定消除、等值传递和常量传递、常量表达式求值、外连接转换为内连接、子查询转换、视图合并等;

  2. 基于成本优化,包括访问方法和连接顺序的选择等;

  3. 执行计划改进,例如表条件下推、访问方法调整、排序避免以及索引条件下推。

逻辑转换

MySQL 优化器首先可能会以不影响结果的方式对查询进行转换,转换的目标是尝试消除某些操作从而更快地执行查询。

mysql> explain select * from user where id >1 and 1=1;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+---------
| id | select_type | table | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+---------
|  1 | SIMPLE      | user  | NULL       | range | PRIMARY       | PRIMARY | 4       | NULL |    1 |   100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+---------
1 row in set, 1 warning (0.02 sec)
​
mysql> show warnings;
+-------+------+--------------------------------------------------------------------------------------------
| Level | Code | Message|
+-------+------+--------------------------------------------------------------------------------------------
| Note  | 1003 | /* select#1 */ select `test`.`user`.`id` AS `id`,`test`.`user`.`name` AS `name`,`test`.`user`.`age` AS `age`,`test`.`user`.`address` AS `address`,`test`.`user`.`birthday` AS `birthday` from `test`.`user` where (`test`.`user`.`id` > 1) |
+-------+------+--------------------------------------------------------------------------------------------
1 row in set (0.01 sec)

显然,查询条件中的 1=1 是完全多余的。没有必要为每一行数据都执行一次计算;删除这个条件也不会影响最终的结果。执行EXPLAIN语句之后,通过SHOW WARNINGS命令可以查看逻辑转换之后的 SQL 语句,从上面的结果可以看出 1=1 已经不存在了。

基于成本的优化

       在真正执行一条查询语句之前,MYSQL的优化器会找出所有可以用来执行该语句的方案,并在对比这些方案后找出成本最低的方案。这个成本最低的方案就是所谓的执行计划。之后才会调用存储引擎提供的接口真正执行查询。总结一下,过程如下:

1、根据搜索条件,找出所有可能使用的索引。

2、计算全表扫描的代价。

3、计算使用不同索引执行查询的代价。

4、对比各种方案的代价,找出成本最低的方案。

为了找到最佳执行计划,优化器需要比较不同的查询方案。随着查询中表的数量增加,可能的执行计划会呈现指数级增长;MySQL里限制一个查询的join表数目上限为61,对于一个有61个表参与的join操作,理论上需要61!(阶乘)次的评估。

所以优化器不可能遍历所有的执行方案,一种更灵活的优化方法是允许用户控制优化器在查找最佳查询计划时的遍历程度。一般来说,优化器评估的计划越少,则编译查询所花费的时间就越少;但另一方面,由于优化器忽略了一些计划,因此可能找到的不是最佳计划。

控制优化程度

MySQL 提供了两个系统变量,可以用于控制优化器的优化程度:

  • optimizer_search_depth,优化器查找的深度。如果该参数大于查询中表的数量,可以得到更好的执行计划,但是优化时间更长;如果小于表的数量,可以更快完成优化,但可能获得的不是最优计划。该参数的默认值为 62;如果不确定是否合适,可以将其设置为 0,让优化器自动决定搜索的深度。

  • optimizer_prune_level, 告诉优化器根据对每个表访问的行数的估计跳过某些方案,这种启发式的方法可以极大地减少优化时间而且很少丢失最佳计划。因此,该参数的默认设置为 1(开启);如果确认优化器错过了最佳计划,可以将该参数设置为 0,不过这样可能导致优化时间的增加。

成本常量

一直在提成本,那么这个代价(成本)是怎么评估的呢?分为两个部分:

  • IO成本:MySQL 读取一个页面的成本。

  • CPU成本:CPU检测一条记录是否符合搜索条件的成本。

总成本=IO成本+CPU成本

从上可以看出,我们需要三种数据

  • 核算IO成本需要读取的页面数量

  • 核算CPU成本需要对比的记录数

  • 每种操作对应的成本常量系数

我们来说说这些成本常量系数,成本常量可以通过 mysql 系统数据库中的 server_costengine_cost 两个表进行查询和设置。

server_cost 中存储的是常规服务器操作的成本估计值:

select * from mysql.server_cost;
cost_name                   |cost_value|last_update        |comment|default_value|
----------------------------|----------|-------------------|-------|-------------|
disk_temptable_create_cost  |          |2018-05-17 10:12:12|       |         10.0|
disk_temptable_row_cost     |          |2018-05-17 10:12:12|       |            1|
key_compare_cost            |          |2018-05-17 10:12:12|       |          0.1|
memory_temptable_create_cost|          |2018-05-17 10:12:12|       |          2.0|
memory_temptable_row_cost   |          |2018-05-17 10:12:12|       |          0.2|
row_evaluate_cost           |          |2018-05-17 10:12:12|       |          0.2|

cost_value 为空表示使用 default_value,其中:

  • disk_temptable_create_costdisk_temptable_row_cost 代表了在基于磁盘的存储引擎(InnoDB 或 MyISAM)中使用内部临时表的评估成本。增加这些值会使得优化器倾向于较少使用内部临时表的查询计划。(group by / distinct等建立临时表)

  • key_compare_cost 代表了比较记录键的评估成本。增加该值将导致需要比较多个键值的查询计划变得更加昂贵。例如,执行 filesort 排序的查询计划比通过索引避免排序的查询计划相对更加昂贵。

  • memory_temptable_create_costmemory_temptable_row_cost 代表了在 MEMORY 存储引擎中使用内部临时表的评估成本。增加这些值会使得优化器倾向于较少使用内部临时表的查询计划。

  • row_evaluate_cost 代表了计算记录条件的评估成本。增加该值会导致检查许多数据行的查询计划变得更加昂贵。例如,与读取少量数据行的索引范围扫描相比,全表扫描变得相对昂贵。

engine_cost 中存储的是特定存储引擎相关操作的成本估计值:

select * from mysql.engine_cost;
engine_name|device_type|cost_name             |cost_value|last_update        |comment|default_value|
-----------|-----------|----------------------|----------|-------------------|-------|-------------|
default    |          0|io_block_read_cost    |          |2018-05-17 10:12:12|       |          1.0|
default    |          0|memory_block_read_cost|          |2018-05-17 10:12:12|       |         0.25|

engine_name 表示存储引擎,“default”表示所有存储引擎,也可以为不同的存储引擎插入特定的数据。cost_value 为空表示使用 default_value。其中,

  • io_block_read_cost 代表了从磁盘读取索引或数据块的成本。增加该值会使读取许多磁盘块的查询计划变得更加昂贵。例如,与读取较少块的索引范围扫描相比,全表扫描变得相对昂贵。

  • memory_block_read_cost 表示从数据库缓冲区读取索引或数据块的成本。

例 子:

1、我们来看一个例子,执行以下语句:

mysql> explain format=json select * from user where birthday between "2000-01-01" and "2020-11-01"; 

+ -------------------------------------------------------------------- +
| EXPLAIN |
+ -------------------------------------------------------------------- +
{
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "9822.60"
    },
    "table": {
      "table_name": "user",
      "access_type": "ALL",
      "possible_keys": [
        "index_birthday"
      ],
      "rows_examined_per_scan": 48308,
      "rows_produced_per_join": 18209,
      "filtered": "37.70",
      "cost_info": {
        "read_cost": "6180.60",
        "eval_cost": "3642.00",
        "prefix_cost": "9822.60",
        "data_read_per_join": "14M"
      },
      "used_columns": [
        "id",
        "sex",
        "name",
        "age",
        "birthday"
      ],
      "attached_condition": "(`test`.`user`.`birthday` between '2000-01-01' and '2020-11-01')"
    }
  }
}

        查询计划显示使用了全表扫描(access_type = ALL),而没有选择 index_birthday。可以在上面看到全表扫描的成本是9822.6,这个值是怎么来的呢?这就得提到MYSQL为每个表维护的一系列的统计信息了。可以通过SHOW TABLE STATUS查看表的统计信息。

查看表 user 的统计信息(show table status like 'user';):

  • Rows:表中的记录条数。对于MyISAM存储引擎,该值是准确的;对于InnoDB,该值是一个估值。

  • Data_length:表占用的存储空间字节数。对于MyISAM存储引擎,该值就是数据文件的大小;对于InnoDB引擎,该值就相当于聚簇索引占用的存储空间的大小。所以对于使用InnoDB引擎的表,Data_length = 聚簇索引的页面数量 * 每个页面的大小(默认16k)。

再来算一下上面的全表扫描的总成本9822.6怎么来的:

聚簇索引的页面数量(IO读取的页面数量) = 2637824 ÷ 16 ÷ 1024 = 161 
I/O成本:161 * 1.0 = 161 
CPU成本:48308 * 0.2 = 9661.6 
总成本:161 + 9661.6 = 9822.6

再看为什么没有选择 index_birthday索引呢?可以通过优化器跟踪可以看到具体原因。

  • 优化器跟踪(optimizer_trace):从MySQL5.6版本开始,optimizer_trace 可支持把MySQL查询执行计划树打印出来,对深入分析SQL执行计划,COST成本都非常有用,打印的内部信息比较全面。):从MySQL5.6版本开始,optimizer_trace 可支持把MySQL查询执行计划树打印出来,对深入分析SQL执行计划,COST成本都非常有用,打印的内部信息比较全面。

优化器跟踪输出主要包含了三个部分:

img

  • join_preparation,准备阶段,返回了字段名扩展之后的 SQL 语句。对于 1=1 这种多余的条件,也会在这个步骤被删除。

  • join_optimization,优化阶段。其中 condition_processing 中包含了各种逻辑转换,经过等值传递之后将条件 id=age 转换为了 age=1。另外 constant_propagation 表示常量传递,trivial_condition_removal 表示无效条件移除。

  • join_execution,执行阶段。

开启optimizer_trace:

mysql> SET optimizer_trace="enabled=on";
Query OK, 0 rows affected (0.00 sec)

使用优化器跟踪查看:

mysql> select * from information_schema.optimizer_trace\G
*************************** 1. row ***************************
                            QUERY: explain format=json select * from user where birthday between "2000-01-01" and "2020-11-01"; 
                            TRACE: {
  "steps": [
    {
      "join_preparation": {
        "select#": 1,
        "steps": [
          {
            "expanded_query": "/* select#1 */ select `user`.`id` AS `id`,`user`.`sex` AS `sex`,`user`.`name` AS `name`,`user`.`age` AS `age`,`user`.`birthday` AS `birthday` from `user` where (`user`.`birthday` between '2000-01-01' and '2020-11-01')"
          }
        ]
      }
    },
    {
      "join_optimization": {
        "select#": 1,
        "steps": [
          {
            "condition_processing": {
              "condition": "WHERE",
              "original_condition": "(`user`.`birthday` between '2000-01-01' and '2020-11-01')",
              "steps": [
                {
                  "transformation": "equality_propagation",
                  "resulting_condition": "(`user`.`birthday` between '2000-01-01' and '2020-11-01')"
                },
                {
                  "transformation": "constant_propagation",
                  "resulting_condition": "(`user`.`birthday` between '2000-01-01' and '2020-11-01')"
                },
                {
                  "transformation": "trivial_condition_removal",
                  "resulting_condition": "(`user`.`birthday` between '2000-01-01' and '2020-11-01')"
                }
              ]
            }
          },
          {
            "substitute_generated_columns": {
            }
          },
          {
            "table_dependencies": [
              {
                "table": "`user`",
                "row_may_be_null": false,
                "map_bit": 0,
                "depends_on_map_bits": [
                ]
              }
            ]
          },
          {
            "ref_optimizer_key_uses": [
            ]
          },
          {
            "rows_estimation": [
              {
                "table": "`user`",
                "range_analysis": {
                  "table_scan": {
                    "rows": 48308,
                    "cost": 9824.7
                  },
                  "potential_range_indexes": [
                    {
                      "index": "PRIMARY",
                      "usable": false,
                      "cause": "not_applicable"
                    },
                    {
                      "index": "index_name",
                      "usable": false,
                      "cause": "not_applicable"
                    },
                    {
                      "index": "index_birthday",
                      "usable": true,
                      "key_parts": [
                        "birthday",
                        "id"
                      ]
                    }
                  ],
                  "setup_range_conditions": [
                  ],
                  "group_index_range": {
                    "chosen": false,
                    "cause": "not_group_by_or_distinct"
                  },
                  "analyzing_range_alternatives": {
                    "range_scan_alternatives": [
                      {
                        "index": "index_birthday",
                        "ranges": [
                          "0x21a00f <= birthday <= 0x61c90f"
                        ],
                        "index_dives_for_eq_ranges": true,
                        "rowid_ordered": false,
                        "using_mrr": false,
                        "index_only": false,
                        "rows": 18210,
                        "cost": 21853,
                        "chosen": false,
                        "cause": "cost"
                      }
                    ],
                    "analyzing_roworder_intersect": {
                      "usable": false,
                      "cause": "too_few_roworder_scans"
                    }
                  }
                }
              }
            ]
          },
          {
            "considered_execution_plans": [
              {
                "plan_prefix": [
                ],
                "table": "`user`",
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "rows_to_scan": 48308,
                      "access_type": "scan",
                      "resulting_rows": 18210,
                      "cost": 9822.6,
                      "chosen": true
                    }
                  ]
                },
                "condition_filtering_pct": 100,
                "rows_for_plan": 18210,
                "cost_for_plan": 9822.6,
                "chosen": true
              }
            ]
          },
          {
            "attaching_conditions_to_tables": {
              "original_condition": "(`user`.`birthday` between '2000-01-01' and '2020-11-01')",
              "attached_conditions_computation": [
              ],
              "attached_conditions_summary": [
                {
                  "table": "`user`",
                  "attached": "(`user`.`birthday` between '2000-01-01' and '2020-11-01')"
                }
              ]
            }
          },
          {
            "refine_plan": [
              {
                "table": "`user`"
              }
            ]
          }
        ]
      }
    },
    {
      "join_explain": {
        "select#": 1,
        "steps": [
        ]
      }
    }
  ]
}

使用全表扫描的总成本为9822.60,使用范围扫描的总成本为 21853。这是因为查询返回了 user表中大部分的数据,通过索引范围扫描,然后再回表反而会比直接扫描表更慢。

2、接下来我们将数据行比较的成本常量 row_evaluate_cost 从 0.2 改为 1,并且刷新内存中的值:

update mysql.server_cost 
set cost_value=1 
where cost_name='row_evaluate_cost';

flush optimizer_costs;

然后重新连接数据库,再次获取执行计划的结果如下:

mysql> explain format=json select * from user where birthday between "2000-01-01" and "2020-11-01"; 

+ -------------------------------------------------------------------- +
| EXPLAIN |
+ -------------------------------------------------------------------- +
{
  "query_block": {
    "select_id": 1,
    "cost_info": {
      "query_cost": "54631.01"
    },
    "table": {
      "table_name": "user",
      "access_type": "range",
      "possible_keys": [
        "index_birthday"
      ],
      "key": "index_birthday",
      "used_key_parts": [
        "birthday"
      ],
      "key_length": "4",
      "rows_examined_per_scan": 18210,
      "rows_produced_per_join": 18210,
      "filtered": "100.00",
      "index_condition": "(`test`.`user`.`birthday` between '2000-01-01' and '2020-11-01')",
      "cost_info": {
        "read_cost": "36421.01",
        "eval_cost": "18210.00",
        "prefix_cost": "54631.01",
        "data_read_per_join": "14M"
      },
      "used_columns": [
        "id",
        "sex",
        "name",
        "age",
        "birthday"
      ]
    }
  }
}

此时,优化器选择的范围扫描(access_type = range),虽然它的成本增加,但是使用全表扫描的代价更高。

row_evaluate_cost 的还原成默认设置并重新连接数据库:

update mysql.server_cost 
set cost_value= null
where cost_name='row_evaluate_cost';

flush optimizer_costs;

控制优化行为

MySQL 提供了一个系统变量 optimizer_switch,用于控制优化器的优化行为。

mysql-> select @@optimizer_switch;

+ -------------------------------------------------------------------- +
|@@optimizer_switch |                                                                                       + -------------------------------------------------------------------- +
index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=on,mrr_cost_based=on,block_nested_loop=on,batched_key_access=off,materialization=on,semijoin=on,loosescan=on,firstmatch=on,duplicateweedout=on,subquery_materialization_cost_based=on,use_index_extensions=on,condition_fanout_filter=on,derived_merge=on,use_invisible_indexes=off,skip_scan=on,hash_join=on|

它的值由一组标识组成,每个标识的值都可以为 on 或 off,表示启用或者禁用了相应的优化行为。

该变量支持全局和会话级别的设置,可以在运行时进行更改。

SET [GLOBAL|SESSION] optimizer_switch='command[,command]...';

优化器和索引提示

虽然通过系统变量 optimizer_switch 可以控制优化器的优化策略,但是一旦改变它的值,后续的查询都会受到影响,除非再次进行设置。

另一种控制优化器策略的方法就是优化器提示(Optimizer Hint)索引提示(Index Hint),它们只对单个语句有效,而且优先级比 optimizer_switch 更高。

优化器提示使用 /*+ … */ 注释风格的语法,可以对连接顺序、表访问方式、索引使用方式、子查询、语句执行时间限制、系统变量以及资源组等进行语句级别的设置。

例如,在没有使用优化器提示的情况下:

优化器选择 employee 作为驱动表,并且使用全表扫描返回 salary = 10000 的数据;然后通过主键查找 department 中的记录。

然后我们通过优化器提示 join_order 修改两个表的连接顺序:

此时,优化器选择了 department 作为驱动表;同时访问 employee 时选择了全表扫描。我们可以再增加一个索引相关的优化器提示 index:

最终,优化器选择了通过索引 idx_emp_dept 查找 employee 中的数据。

其他还有很多,比如 USE INDEX 提示优化器使用某个索引,IGNORE INDEX 提示优化器忽略某个索引,FORCE INDEX 强制使用某个索引。。。。等等。

 

总结

MySQL 优化器使用基于成本的优化方式,利用数据字典和统计信息选择 SQL 语句的最佳执行方式。同时,MySQL 为我们提供了控制优化器的各种选项,包括控制优化程度、设置成本常量、统计信息收集、启用/禁用优化行为以及使用优化器提示等。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

MYSQL查询优化器 的相关文章

随机推荐

  • Struts简介

    一 Struts英文单词意思 支柱 支架 来源于建筑和旧式飞机使用的金属支架 二 定义 Struts是流行和成熟的基于MVC设计模式的Web应用程序框架 Struts在软件开发中 是一个非常优秀的框架 它先是Jakarta项目的一个子项目
  • 微服务常见解决方案和高并发常见解决方案、以上特殊解决方案

    我确实比较懒 有些东西总是会写到自己的笔记本上或者在线笔记中 一般很少总结写到博客中 闲来无事确实总结了一些 如果你需要 可以私信聊聊 不敢说有特别好特别完美的方案 但是从个人视角结合知识再结合经验还是有点可说的内容的 如果你需要请私信
  • Parameter ‘name‘ not found. Available parameters are [0, 1, param1, param2]] with root cause

    SpringBoot环境 测试查询数据库 第一次直接通过dao层的注解查询 全局查询没问题 加入参数就报错了 提示说传的参数找不到 打印出来都有值 最后查了一下 这是错误的接口方法 查询注解里的参数有问题 修改为
  • TinyKv流程梳理三

    split流程 处理协程启动 func bs Raftstore startWorkers peers peer ctx bs ctx workers bs workers router bs router bs wg Add 2 raft
  • RecycleView

    鸿洋 Android RecyclerView 使用完全解析 体验艺术般的控件 http blog csdn net lmj623565791 article details 45059587 Android RecycleView 实现滑
  • 虚拟机服务器之间文件拷贝与传输

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 前言 一 scp scp定义 scp可以实现服务器与服务器之间的数据拷贝 from server1 to server2 二 rsync rsync主要用于备份和镜
  • 开漏输出、推挽输出的区别

    前言 background 测试相关设备引脚输出 使用示波器时发现部分引脚需外接上拉电阻至高电平才能在示波器观察到高阻态 为了深究其中原理 查阅了相关资料 发现知乎中有一篇对这两种输出描述得清晰易懂的文章 此时才真正了解信号高阻态和高电平输
  • SpringBoot--基础--04--拦截器

    SpringBoot 基础 04 拦截器 拦截器 实现HandlerInterceptor接口 注册拦截器 一 代码结构 MyMvcConfig 使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能 Conf
  • 同步方法及同步代码块

    synchronized方法和synchronized块 synchronized方法必须获得对象的锁才能执行 否则线程阻塞 方法一旦执行 就独占此锁 直到方法返回才释放锁 后面被阻塞的线程才能获得这个锁 继续执行 synchronized
  • 谷歌取消了2000多人的offer,特殊时期如何应对裁员?

    就在刚刚 CNBC报道 科技大厂谷歌有上千人饭碗保不住了 图片来自NBC 版权属于原作者 谷歌缩减招聘 据悉这部分人主要是谷歌的合同工和临时工 纽约时报 报道 谷歌取消了2000多人的工作录取 上个月 谷歌首席执行官劈柴君向员工承认 由于新
  • 字符串 字符串中的第一个唯一字符

    LC 字符串中的第一个唯一字符 给定一个字符串 s 找到 它的第一个不重复的字符 并返回它的索引 如果不存在 则返回 1 Swift 实现 func firstUniqChar s String gt Int 创建字符数组 var arra
  • [HTTP] 01一张思维导图带你领略HTTP发展史

    HTTP是什么 HTTP作为一个较常见的应用协议 在面试过程中经常会对其相关内容进行考察 其发展史亦是重点 HTTP是什么 HTTP为Hyper Text Transfer Protocol的简写 中译超文本传输协议 是在计算机网络里面点对
  • 网络原理:ISO/OSI参考模型

    1 ISO OSI参考模型 1 1 各层功能 1 物理层功能 物理层是OSI参考模型的最低层 它利用传输介质为数据链路层提供物理连接 保证二进制位流的透明传送 2 数据链路层 数据链路层是为网络层提供服务的 解决两个相邻结点之间的通信问题
  • C# 学习笔记--个人学习使用 <1>

    C 学习笔记 Chapter 1 C 比较软的基础部分 Section 1 类与命名空间 Part 1 命名空间 NameSpace Part 2 类 Class Section 2 基本元素 Section 3 数据类型 Part 1 什
  • 使用opencv rotate函数实现图片旋转

    使用rotate函数 旋转图片 rotate src dst ROTATE 90 CLOCKWISE 使用rotate 旋转图片 src是源图片的mat dts是目标图片的mat 第3个参数是旋转的角度 有以下4个选项 ROTATE 90
  • android app缓存机制会自动清除,深入理解Android缓存机制(一)缓存简介

    概述 说起缓存 大家可能很容易想到Http的缓存机制 LruCache 其实缓存最初是针对于网络而言的 也是狭义上的缓存 广义的缓存是指对数据的复用 我这里提到的也是广义的缓存 比较常见的是内存缓存以及磁盘缓存 不过要想进一步理解缓存体系
  • Python内置函数详解,进阶必备

    内置函数就是Python给你提供的 拿来直接用的函数 比如print input等 截止到python版本3 6 2 python一共提供了68个内置函数 具体如下 本文将这68个内置函数综合整理为12大类 正在学习Python基础的读者一
  • 关于SDC时钟约束的事

    1 quartus的SDC约束就跟xilinx的ucf约束文件一样 2 主要用途一般是 一从输入端口到寄存器 二寄存器到寄存器 通过设定时钟频率方式进行约束 三寄存器到输出 四创建时钟约束命令 五时钟延迟约束 六时钟抖动约束 七输入和输出延
  • Android实现简单的相册

    用手机浏览靓照也是件非常惬意的事了 然而如何实现这一功能呢 其实也并不难 下面是一个简单的例子 功能为 主屏幕上显示用户选择的靓照 屏幕下面滚动显示靓照集 点击即可浏览 如下图所示 实现代码为 这里图片没法提供了 可以设置自己喜欢的图片 R
  • MYSQL查询优化器

    MYSQL 逻辑结构 MySQL 使用典型的客户端 服务器 Client Server 结构 体系结构大体可以分为三层 客户端 服务器层以及存储引擎层 其中 服务器层又包括了连接管理 查询缓存 SQL 接口 解析器 优化器 缓冲与缓存以及各