mysql8.x版本_select语句源码跟踪

总结

源码基于8.0.34版本分析,函数执行流程含义大致如下:

  1. do_command 方法从连接中读取命令并执行,调用 dispatch_command 对命令进行分发。
  2. dispatch_command 调用 mysql_parse 对命令进行解析,如果遇到一条语句用 ; 分隔多条命令,则会循环调用 mysql_parse,直到出现,解析错误、线程被kill,则不再继续循环。解析时,如遇到语法错误直接返回 error,否则解析结束后,通过判断当前用户是否有该表权限来决定执行或返回错误。
  3. mysql_parse 解析无语法错误,且权限无问题,会调用 mysql_execute_command 执行。
  4. execute 方法执行准备工作、锁表、优化器优化、执行、清理,在内部会调用 Sql_cmd_dml::execute_inner 方法。
  5. execute_inner 方法真正执行优化器优化、记录开销、执行语句。
  6. ExecuteIteratorQuery 方法最终会迭代处理语句结果,读取并通过网络缓冲区逐条发送给客户端。

调用堆栈图

THD::send_result_set_row(THD * const this, const mem_root_deque<Item*> & row_items) (\root\code\mysql-8.0.34\sql\sql_class.cc:2863)
Query_result_send::send_data(Query_result_send * const this, THD * thd, const mem_root_deque<Item*> & items) (\root\code\mysql-8.0.34\sql\query_result.cc:100)
Query_expression::ExecuteIteratorQuery(Query_expression * const this, THD * thd) (\root\code\mysql-8.0.34\sql\sql_union.cc:1785)
Query_expression::execute(Query_expression * const this, THD * thd) (\root\code\mysql-8.0.34\sql\sql_union.cc:1823)
Sql_cmd_dml::execute_inner(Sql_cmd_dml * const this, THD * thd) (\root\code\mysql-8.0.34\sql\sql_select.cc:1022)
Sql_cmd_dml::execute(Sql_cmd_dml * const this, THD * thd) (\root\code\mysql-8.0.34\sql\sql_select.cc:793)
mysql_execute_command(THD * thd, bool first_level) (\root\code\mysql-8.0.34\sql\sql_parse.cc:4719)
dispatch_sql_command(THD * thd, Parser_state * parser_state) (\root\code\mysql-8.0.34\sql\sql_parse.cc:5368)
dispatch_command(THD * thd, const COM_DATA * com_data, enum_server_command command) (\root\code\mysql-8.0.34\sql\sql_parse.cc:2054)
do_command(THD * thd) (\root\code\mysql-8.0.34\sql\sql_parse.cc:1439)
handle_connection(void * arg) (\root\code\mysql-8.0.34\sql\conn_handler\connection_handler_per_thread.cc:302)
pfs_spawn_thread(void * arg) (\root\code\mysql-8.0.34\storage\perfschema\pfs.cc:3042)

源码下载

https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-boost-8.0.34.tar.gz

新建测试表

CREATE DATABASE test_db;

use test_db;

CREATE TABLE `t1` (
  `id` int unsigned NOT NULL AUTO_INCREMENT,
  `str1` varchar(255) DEFAULT '',
  `i1` int DEFAULT '0',
  `i2` int DEFAULT '0',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;

INSERT INTO t1(str1, i1, i2) VALUES
('s1', NULL, NULL),
('s2', 20, NULL),
('s3', 30, 31),
('s4', 40, 41),
('s5', 50, 51),
('s6', 60, 61),
('s7', 70, 71),
('s8', 80, 81);

select * from t1 where i2 > 20 and (i1 = 50 or i1 = 80);

入口函数

handle_connection

connection_handler_per_thread.cc:245 -> 最后走的是do_command()函数

extern "C" {
static void *handle_connection(void *arg) {
    my_thread_init();
    for (;;) {
        THD *thd = init_new_thd(channel_info);

        while (thd_connection_alive(thd)) {
            //执行具体逻辑
            if (do_command(thd)) break;
        }
        end_connection(thd);

        delete thd;
    }
    my_thread_end();
    my_thread_exit(nullptr);
    return nullptr;
}
}

do_command

sql_parse.cc:1439 执行命令的
return_value = dispatch_command(thd, &com_data, command);

bool do_command(THD *thd) {
    bool return_value;

    /*
    将执行阻塞读取操作来从客户端接收数据。当线程收到来自客户端的下一个命令、连接被关闭或经过了"net_wait_timeout"(网络等待超时)指定的秒数时,读取操作将被中断。
    */
    net = thd->get_protocol_classic()->get_net();
    my_net_set_read_timeout(net, thd->variables.net_wait_timeout);
    net_new_transaction(net);

    /* 恢复线程超时限制值*/
    my_net_set_read_timeout(net, thd->variables.net_read_timeout);

    //执行代码逻辑
    return_value = dispatch_command(thd, &com_data, command);
    
    //thd->get_protocol_classic():这部分代码获取了当前线程(thd)所使用的协议类,即MySQL的经典协议。
    //get_output_packet():这部分代码从协议类中获取输出数据包。
    //shrink(thd->variables.net_buffer_length):这部分代码是用来缩小输出数据包的大小。
    //shrink()方法则是将输出数据包的大小调整为这个值。这样做的目的是为了减少内存占用,提高程序的性能。
    thd->get_protocol_classic()->get_output_packet()->shrink(
            thd->variables.net_buffer_length);

    return return_value;
}

dispatch_command

sql_parse.cc:5368

bool dispatch_command(THD *thd, const COM_DATA *com_data,enum enum_server_command command) {
    thd->set_command(command);
    thd->set_query_id(next_query_id());
switch (command) {
    case COM_QUERY: {
        //从数据包中读取查询语句并将其存储在thd->query中
        alloc_query(thd, com_data->com_query.query,com_data->com_query.length);
        //放置参数
        copy_bind_parameter_values(thd, com_data->com_query.parameters,com_data->com_query.parameter_count);
        //执行线程
        dispatch_sql_command(thd, &parser_state);
        //重置连接
        thd->bind_parameter_values = nullptr;
        thd->bind_parameter_values_count = 0;
        break;
    }
}

//以下是重置线程的命令操作
done:
    thd->update_slow_query_status();
    if (thd->killed) thd->send_kill_message();
    thd->send_statement_status();
    thd->reset_query();
    thd->set_command(COM_SLEEP);
    thd->lex->sql_command = SQLCOM_END;
    thd->mem_root->Clear();
    return error;
}

dispatch_sql_command

void dispatch_sql_command(THD *thd, Parser_state *parser_state) {
    DBUG_PRINT("dispatch_sql_command", ("query: '%s'", thd->query().str));
    // thd->query().str 就是传入的字符串
    //初始化了用于此查询的成本模型,并确保当前查询块为空
    lex_start(thd);

    LEX *lex = thd->lex;
    const char *found_semicolon = nullptr;

    // 检查当前执行的语句是否有错误。如果没有错误,它会调用parse_sql函数解析SQL语句,并在解析成功后调用invoke_post_parse_rewrite_plugins函数。
    // qlen用于解析SQL语句并设置查询长度
    size_t qlen = 0;
    // da 的全称是 Statement Descriptor Accessor  get_stmt_da() 函数返回一个指向语句描述符对象的指针,该对象包含了关于当前执行的语句的信息。
    bool err = thd->get_stmt_da()->is_error();
    if (!err) {
        err = parse_sql(thd, parser_state, nullptr);
        // found_semicolon 就是char*类型的 分号
        found_semicolon = parser_state->m_lip.found_semicolon;
        qlen = found_semicolon ? (found_semicolon - thd->query().str): thd->query().length;

        if (!thd->is_error() && found_semicolon && (ulong)(qlen)) {
            thd->set_query(thd->query().str, qlen - 1);
        }
    }
    //根据当前的SQL命令和触发器类型,为每个表设置正确的触发器事件类型,以便在执行语句时正确处理触发器。
    lex->set_trg_event_type_for_tables();
    //执行Sql语句
    mysql_execute_command(thd, true);
    //清理资源
    thd->lex->destroy();
    thd->end_statement();
    thd->cleanup_after_query();
}

mysql_execute_command

int mysql_execute_command(THD *thd, bool first_level) {
    int res = false;
    LEX *const lex = thd->lex;
    //first_lists_tables_same()的函数,它的作用是将第一个最外层查询的第一个本地表移动到全局表列表的第一个位置。
    // 主要用于处理包含子查询的查询,因为在这种情况下,子查询的表会先进入全局表列表。
    lex->first_lists_tables_same();

    /* Update system variables specified in SET_VAR hints.  更新hint */
    if (lex->opt_hints_global && lex->opt_hints_global->sys_var_hint)
    lex->opt_hints_global->sys_var_hint->update_vars(thd);

    //SQLCOM_SELECT  此处选择特别多,指显示select查询主线
    switch (lex->sql_command) {
        case SQLCOM_DROP_SRS: {
            res = lex->m_sql_cmd->execute(thd);
            break;
        }
    }
    return res || thd->is_error();
}

execute

bool Sql_cmd_dml::execute(THD *thd) {
    lex = thd->lex;
    Query_expression *const unit = lex->unit;
    //判断语句是否prepare
    if (!is_prepared()) {
        if (prepare(thd)) goto err;
    } else {
        /*这段代码是关于准备一个预处理语句(Prepared statement),打开在语句中引用的表,并检查执行该语句所需的权限。*/
        cleanup(thd);
        if (open_tables_for_query(thd, lex->query_tables, 0)) goto err;
        // Bind table and field information   绑定表和字段信息
        if (restore_cmd_properties(thd)) return true;
        if (check_privileges(thd)) goto err;
        if (m_lazy_result) {
            Prepared_stmt_arena_holder ps_arena_holder(thd);
            if (result->prepare(thd, *unit->get_unit_column_types(), unit)) goto err;
            m_lazy_result = false;
        }
    }
    //事务开始
    lex->set_exec_started();
    // 将成本计算置零
    thd->clear_current_query_costs();
    if (lock_tables(thd, lex->query_tables, lex->table_count, 0)) goto err;  //锁表
    // 执行语句
    if (execute_inner(thd)) goto err;

    // 释放资源
    lex->cleanup(false);
    // 保存当前语句开销
    thd->save_current_query_costs();
    // 记录当前查询到的行数
    thd->update_previous_found_rows();
    return false;
}

prepare(THD *thd)

sql\sql_select.cc:549

bool Sql_cmd_dml::prepare(THD *thd) {
    //用于对未准备好的SELECT语句进行授权预检查。这个函数会检查我们是否具有查询涉及的所有表(以及可能涉及的其他实体)的权限。
    if (precheck(thd)) goto err;
    /* 在MySQL数据库中打开表并展开视图的。在执行查询(不作为执行的一部分)时,它会获取S元数据锁而不是SW锁,以与同时进行的LOCK TABLES WRITE和全局读锁保持兼容。 */
    if (open_tables_for_query(
            thd, lex->query_tables,
            needs_explicit_preparation() ? MYSQL_OPEN_FORCE_SHARED_MDL : 0)) {
        if (thd->is_error())  // @todo - dictionary code should be fixed
            goto err;
        if (error_handler_active) thd->pop_internal_handler();
        lex->cleanup(false);
        return true;
    }
    return false;
}

lock_tables

bool lock_tables(THD *thd, Table_ref *tables, uint count, uint flags) {
    if (!(thd->lock = mysql_lock_tables(thd, start, (uint)(ptr - start), flags)))
        return true;
    thd->lex->lock_tables_state = Query_tables_list::LTS_LOCKED;
    int ret = thd->decide_logging_format(tables);
    return ret;
}

//保留堆栈
//get_lock_data(THD * thd, TABLE ** table_ptr, size_t count, uint flags) (\root\code\mysql-8.0.34\sql\lock.cc:686)
//mysql_lock_tables(THD * thd, TABLE ** tables, size_t count, uint flags) (\root\code\mysql-8.0.34\sql\lock.cc:327)
//lock_tables(THD * thd, Table_ref * tables, uint count, uint flags) (\root\code\mysql-8.0.34\sql\sql_base.cc:6899)

execute_inner

bool Sql_cmd_dml::execute_inner(THD *thd) {
  Query_expression *unit = lex->unit;
  //优化器对sql进行优化并查询,成功直接返回
  if (unit->optimize(thd, /*materialize_destination=*/nullptr,
                     /*create_iterators=*/true, /*finalize_access_paths=*/true))
    return true;

  // Calculate the current statement cost.  // 计算当前查询开销
  accumulate_statement_cost(lex);

  // 如果是explain语句,则不真正执行,否则执行
  lex->set_exec_completed();
  if (lex->is_explain()) {
    if (explain_query(thd, thd, unit)) return true; /* purecov: inspected */
  } else {
    if (unit->execute(thd)) return true;
  }

  return false;
}

accumulate_statement_cost

sql_select.cc:881

void accumulate_statement_cost(const LEX *lex) {
    Opt_trace_context *trace = &lex->thd->opt_trace;
    Opt_trace_disable_I_S disable_trace(trace, true);

    double total_cost = 0.0;
    for (const Query_block *query_block = lex->all_query_blocks_list;
         query_block != nullptr;
         query_block = query_block->next_select_in_list()) {
        if (query_block->join == nullptr) continue;

        // Get the cost of this query block.
        double query_block_cost = query_block->join->best_read;

        // 在处理子查询时。如果子查询是非缓存的(non-cacheable),那么需要估计它执行的次数,并相应地调整成本。
        const Item_subselect *item = query_block->master_query_expression()->item;
        if (item != nullptr && !query_block->is_cacheable())
            query_block_cost *= calculate_subquery_executions(item, trace);

        total_cost += query_block_cost;
    }
    lex->thd->m_current_query_cost = total_cost;
}

execute

bool Query_expression::execute(THD *thd) {
    //查询数据
    return ExecuteIteratorQuery(thd);
}
ExecuteIteratorQuery
bool Query_expression::ExecuteIteratorQuery(THD *thd) {
    Opt_trace_context *const trace = &thd->opt_trace;
    Opt_trace_object trace_wrapper(trace);
    Opt_trace_object trace_exec(trace, "join_execution");
    if (is_simple()) {
        trace_exec.add_select_number(first_query_block()->select_number);
    }
    Opt_trace_array trace_steps(trace, "steps");
    // 保存结果字段,提前声明
    mem_root_deque<Item *> *fields = get_field_list();
    Query_result *query_result = this->query_result();
    //标记开始
    set_executed();
    ha_rows *send_records_ptr;
    if (is_simple()) {
        send_records_ptr = &first_query_block()->join->send_records;
    } else if (set_operation()->m_is_materialized) {
        send_records_ptr = &query_term()->query_block()->join->send_records;
    } else {
        send_records_ptr = &send_records;
    }
    *send_records_ptr = 0;
    thd->get_stmt_da()->reset_current_row_for_condition();
    {
        //循环读取数据
        for (;;) {    // 使用 m_root_iterator 迭代器依次读取查询到的结果行 
            int error = m_root_iterator->Read();
            if (error > 0 || thd->is_error())  // Fatal error
                return true;
            else if (error < 0)
                break;
            else if (thd->killed)  // Aborted by user
            {
                thd->send_kill_message();
                return true;
            }
            ++*send_records_ptr;
            if (query_result->send_data(thd, *fields)) {
                return true;
            }
            thd->get_stmt_da()->inc_current_row_for_condition();
        }
    }
    thd->current_found_rows = *send_records_ptr;
    return query_result->send_eof(thd);
}
Read
int FilterIterator::Read() {
    for (;;) {
        int err = m_source->Read();//读数据
        return 0;
    }
}
Read
TableScanIterator::Read(TableScanIterator * const this) (\root\code\mysql-8.0.34\sql\iterators\basic_row_iterators.cc:219)
FilterIterator::Read(FilterIterator * const this) (\root\code\mysql-8.0.34\sql\iterators\composite_iterators.cc:76)
Query_expression::ExecuteIteratorQuery(Query_expression * const this, THD * thd) (\root\code\mysql-8.0.34\sql\sql_union.cc:1770)
Query_expression::execute(Query_expression * const this, THD * thd) (\root\code\mysql-8.0.34\sql\sql_union.cc:1823)
Sql_cmd_dml::execute_inner(Sql_cmd_dml * const this, THD * thd) (\root\code\mysql-8.0.34\sql\sql_select.cc:1022)
Sql_cmd_dml::execute(Sql_cmd_dml * const this, THD * thd) (\root\code\mysql-8.0.34\sql\sql_select.cc:793)
mysql_execute_command(THD * thd, bool first_level) (\root\code\mysql-8.0.34\sql\sql_parse.cc:4719)
dispatch_sql_command(THD * thd, Parser_state * parser_state) (\root\code\mysql-8.0.34\sql\sql_parse.cc:5368)
dispatch_command(THD * thd, const COM_DATA * com_data, enum_server_command command) (\root\code\mysql-8.0.34\sql\sql_parse.cc:2054)
do_command(THD * thd) (\root\code\mysql-8.0.34\sql\sql_parse.cc:1439)
handle_connection(void * arg) (\root\code\mysql-8.0.34\sql\conn_handler\connection_handler_per_thread.cc:302)
pfs_spawn_thread(void * arg) (\root\code\mysql-8.0.34\storage\perfschema\pfs.cc:3042)

int TableScanIterator::Read() {
    int tmp;
    if (table()->is_union_or_table()) {
        while ((tmp = table()->file->ha_rnd_next(m_record))) {
            /*
             ha_rnd_next can return RECORD_DELETED for MyISAM when one thread is
             reading and another deleting without locks.
             */
            if (tmp == HA_ERR_RECORD_DELETED && !thd()->killed) continue;
            return HandleError(tmp);
        }
        if (m_examined_rows != nullptr) {
            ++*m_examined_rows;
        }
    } else {
        while (true) {
            if (m_remaining_dups == 0) {  // always initially
                while ((tmp = table()->file->ha_rnd_next(m_record))) {
                    if (tmp == HA_ERR_RECORD_DELETED && !thd()->killed) continue;
                    return HandleError(tmp);
                }
                if (m_examined_rows != nullptr) {
                    ++*m_examined_rows;
                }

                // Filter out rows not qualifying for INTERSECT, EXCEPT by reading
                // the counter.
                const ulonglong cnt =
                        static_cast<ulonglong>(table()->set_counter()->val_int());
                if (table()->is_except()) {
                    if (table()->is_distinct()) {
                        // EXCEPT DISTINCT: any counter value larger than one yields
                        // exactly one row
                        if (cnt >= 1) break;
                    } else {
                        // EXCEPT ALL: we use m_remaining_dups to yield as many rows
                        // as found in the counter.
                        m_remaining_dups = cnt;
                    }
                } else {
                    // INTERSECT
                    if (table()->is_distinct()) {
                        if (cnt == 0) break;
                    } else {
                        HalfCounter c(cnt);
                        // Use min(left side counter, right side counter)
                        m_remaining_dups = std::min(c[0], c[1]);
                    }
                }
            } else {
                --m_remaining_dups;  // return the same row once more.
                break;
            }
            // Skipping this row
        }
        if (++m_stored_rows > m_limit_rows) {
            return HandleError(HA_ERR_END_OF_FILE);
        }
    }
    return 0;
}
ha_rnd_next
int handler::ha_rnd_next(uchar *buf) {
    int result;

    // Set status for the need to update generated fields
    m_update_generated_read_fields = table->has_gcol();

    MYSQL_TABLE_IO_WAIT(PSI_TABLE_FETCH_ROW, MAX_KEY, result,
                        { result = rnd_next(buf); })
    if (!result && m_update_generated_read_fields) {
        result = update_generated_read_fields(buf, table);
        m_update_generated_read_fields = false;
    }
    table->set_row_status_from_handler(result);
    return result;
}

send_data
bool Query_result_send::send_data(THD *thd,
                                  const mem_root_deque<Item *> &items) {
    Protocol *protocol = thd->get_protocol();
    protocol->start_row();
    if (thd->send_result_set_row(items)) {
        protocol->abort_row();
        return true;
    }
    
    thd->inc_sent_row_count(1);
    return protocol->end_row();
}

bool THD::send_result_set_row(const mem_root_deque<Item *> &row_items) {
    char buffer[MAX_FIELD_WIDTH];
    String str_buffer(buffer, sizeof(buffer), &my_charset_bin);
    for (Item *item : VisibleFields(row_items)) {
        if (item->send(m_protocol, &str_buffer) || is_error()) return true;
        str_buffer.set(buffer, sizeof(buffer), &my_charset_bin);
    }
    return false;
}

其他

HAVE_PSI_THREAD_INTERFACE的作用

HAVE_PSI_THREAD_INTERFACE 是一个编译器宏,用于表示是否支持 PSI(Process Status Interface)线程接口。PSI 是 Linux 内核中用于获取进程状态信息的一种机制。在 C++ 编程语言中,这个宏通常用于条件编译,以便根据编译器和系统的支持情况来选择性地包含或排除与 PSI 相关的代码。

例如,如果你想在支持 PSI 的系统上使用 psi_thread_info 结构体,你可以这样使用:

#include <linux/psi.h>
int main() {
#ifdef HAVE_PSI_THREAD_INTERFACE
    psi_thread_info info;
    // 使用 info 进行相关操作
#else
    // 不支持 PSI 的处理逻辑
#endif
    return 0;
}

TGID

GTID是全局事务标识符(Global Transaction Identifier)的缩写,它是一个唯一标识一个分布式事务的数字。在MySQL中,GTID被用于确保在主从复制环境中数据的一致性。
gtid_consistency_violation_state变量表示当前线程是否存在GTID一致性违规状态。如果存在GTID一致性违规,那么可以通过回滚操作来修复数据不一致的问题。

int mysql_execute_command(THD *thd, bool first_level) {
  int res = false;
  LEX *const lex = thd->lex;
  /* first Query_block (have special meaning for many of non-SELECTcommands) Query_block是一个关键字,它表示一个查询块的开始。对于许多非SELECT命令,这个查询块具有特殊意义。 */
  Query_block *const query_block = lex->query_block;
  /* first table of first Query_block */
  Table_ref *const first_table = query_block->get_table_list();
  /* list of all tables in query */
  Table_ref *all_tables;
  // keep GTID violation state in order to roll it back on statement failure  gtid_consistency_violation_state变量表示当前线程是否存在GTID一致性违规状态。如果存在GTID一致性违规,那么可以通过回滚操作来修复数据不一致的问题。
  bool gtid_consistency_violation_state = thd->has_gtid_consistency_violation;

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

猫头虎带您探索Go语言的魅力:GoLang程序员必备的第三方库大盘点 ‍ ‍

猫头虎带您探索Go语言的魅力&#xff1a;GoLang程序员必备的第三方库大盘点 ‍ &#x1f680;&#x1f431;‍&#x1f4bb; 博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#x…

Netty Review - 深入探讨Netty的心跳检测机制:原理、实战、IdleStateHandler源码分析

文章目录 概述心跳检测Code模拟心跳超时正常情况 IdleStateHandler源码分析channelReadchannelActiveinitialize 概述 心跳检测 Netty 的心跳检测机制是一种用于保持网络连接活跃的机制&#xff0c;它通过定期发送和接收特定的消息&#xff08;心跳包&#xff09;来确保客户端…

Java复习二—CH3-Exception 、CH4-I/O

CH3 Java Exception (异常) 3.1 Exception&#xff08;异常&#xff09;和 Error&#xff08;错误&#xff09; 异常能被程序本身可以处理&#xff0c;错误是无法处理 大多数错误与代码编写者执行的操作无关&#xff0c;而表示代码运行时 JVM 出现的问题 异常发生的原因有很…

电子科大软件系统架构设计——软件建模详细设计

文章目录 软件建模详细设计概述软件建模详细设计目标软件建模详细设计原则开闭原则里氏 (Liskov) 替换原则依赖倒置原则接口分离原则单一职责原则最少知识原则&#xff08;迪米特法则&#xff09;高内聚原则松耦合原则可重用原则 软件建模详细设计内容 UML 软件静态结构视图建模…

现代控制理论-李雅普诺夫

现代控制理论-李雅普诺夫 单输入单输出系统&#xff08;BIBO&#xff09;的系统函数如下&#xff1a; 则&#xff0c;该系统的能控标准型&#xff08;能空性&#xff09;为&#xff1a; 能观性&#xff1a; 李雅普诺夫下的稳定性&#xff1a; 李雅普诺夫下的渐进稳定性&a…

Ubuntu20.04纯命令配置PCL(点云库)

Ubuntu20.04纯命令配置PCL&#xff08;点云库&#xff09; 最近在学习点云库&#xff08;PCL&#xff09;的使用&#xff0c;第一步就是在自己的电脑安装配置PCL。 首先&#xff0c;对于ubuntu 16.04以上版本&#xff0c;可以直接使用命令进行安装&#xff0c;新建好一个文件夹…

小程序radio单选框回显

话不多说&#xff0c;效果图如下&#xff1a; 具体代码如下&#xff1a; <radio-group name"radio" bindchange"getSex"><label><radio value"1" checked"{{xingbie1}}" />男</label><label><radio…

Python序列之元组

系列文章目录 Python序列之列表 Python序列之元组 系列文章目录前言一、元组是什么&#xff1f;二、元组操作1.元组的创建&#xff08;1&#xff09;通过()创建。小括号可以省略。&#xff08;2&#xff09;通过tuple()函数创建。&#xff08;3&#xff09;通过生成器推导式创…

HBase 集群搭建

文章目录 安装前准备兼容性官方网址 集群搭建搭建 Hadoop 集群搭建 Zookeeper 集群解压缩安装配置文件高可用配置分发 HBase 文件 服务的启停启动顺序停止顺序 验证进程查看 Web 端页面 安装前准备 兼容性 1&#xff09;与 Zookeeper 的兼容性问题&#xff0c;越新越好&#…

ubuntu docker 进入容器内使用ping 指令,提示bash: ping: command not found问题

解决方法 #更新软件列表 apt update #安装 ping 工具iputils-ping apt install -y iputils-ping #最后使用ping一下容器ping 172.18.0.3欢迎关注我的公众号“点滴分享技术猿”&#xff0c;原创技术文章第一时间推送。

WT2605C音频蓝牙语音芯片:单芯片实现蓝牙+MP3+BLE+电话本多功能应用

在当今的电子产品领域&#xff0c;多功能、高集成度成为了一种趋势。各种产品都需要具备多种功能&#xff0c;以满足用户多样化的需求。针对这一市场趋势&#xff0c;唯创知音推出了一款集成了蓝牙、MP3播放、BLE和电话本功能的音频蓝牙语音芯片——WT2605C&#xff0c;实现了单…

超维空间S2无人机使用说明书——21、VINS视觉定位仿真

引言&#xff1a;为了实现室内无人机的定位功能&#xff0c;S系列无人机配置了VINS-FUSION定位环境&#xff0c;主要包含了仿真跑数据集和实际操作部分。为了提前熟悉使用原理&#xff0c;可以先使用仿真环境跑数据集进行学习和理解 硬件&#xff1a;1080P显示器、Jetson orin…

博弈论:理解决策背后的复杂动态

1.基本概念 博弈论是一门研究具有冲突和合作元素决策制定的数学理论。它不仅适用于经济学&#xff0c;还广泛应用于政治学、心理学、生物学等领域。博弈论的核心在于分析参与者&#xff08;称为“玩家”&#xff09;在特定情境下的策略选择&#xff0c;以及这些选择如何影响最…

真实工作中,做接口测试的流程一般是怎么样的?一定要学透彻

在讲接口流程测试之前&#xff0c;首先需要给大家申明下&#xff1a;接口测试对于测试人员而言&#xff0c;非常非常重要&#xff0c;懂功能测试接口测试&#xff0c;就能在企业中拿到一份非常不错的薪资。 这么重要的接口测试&#xff0c;一般也是面试笔试必问。为方便大家更…

界面控件DevExpress WinForms PDF Viewer,让应用轻松显示PDF文档(一)

DevExpress WinForms的PDF Viewer&#xff08;查看器&#xff09;控件使用户可以轻松地在Windows应用程序中直接显示PDF文档&#xff0c;而无需在最终用户的机器上安装外部PDF阅读器。 DevExpress WinForms有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业…

【贪心】最小生成树Kruskal算法Python实现

文章目录 [toc]问题描述最小生成树的性质证明 Kruskal算法时间复杂性Python实现 个人主页&#xff1a;丷从心 系列专栏&#xff1a;贪心算法 问题描述 设 G ( V , E ) G (V , E) G(V,E)是无向连通带权图&#xff0c; E E E中每条边 ( v , w ) (v , w) (v,w)的权为 c [ v ] …

听GPT 讲Rust源代码--src/tools(24)

File: rust/src/tools/clippy/clippy_lints/src/types/borrowed_box.rs 在Rust源代码中的rust/src/tools/clippy/clippy_lints/src/types/borrowed_box.rs文件是Clippy项目的一个规则&#xff0c;用于检查可能是误用或错误的Box引用情况。 Rust中的Box是一个堆分配的值的所有权…

京东员工:35岁,我只是年龄大了,又不是傻了残疾了,为啥找不到与3-5年经验者平等的面试机会?...

* 你好&#xff0c;我是前端队长&#xff0c;在职场&#xff0c;玩副业&#xff0c;文末有福利! 年龄大了&#xff0c;并不代表我们已经过时了。超过35岁的我们&#xff0c;面对着职场的冷风&#xff0c;经验丰富却常被误解。为什么年轻人能轻松得到的面试机会&#xff0c;到我…

【C语言刷题每日一题#牛客网BC68】——X形图案

问题描述 思路分析 首先根据输入的描述&#xff0c;多组输入需要将scanf放在循环中来实现 #include<stdio.h> int main() {int a 0;while (scanf("%d", &a) ! EOF){} } 完成了输入之后&#xff0c;再来分析输出——输出的是一个由“*”组成的对称的X形…

vue3项目 - 目录调整

省流 删除默认文件&#xff0c;修改代码 ---> 调整目录结构 ---> 添加全局样式和图片&#xff0c;安装预处理器 具体步骤&#xff1a; 1. 删除初始化的默认文件&#xff0c;修改剩余代码 清空 assets、components、stores、views文件夹里的文件&#xff0c;仅留核心文件…