zoukankan      html  css  js  c++  java
  • MySQL源代码解读(一)

    MySQL启动流程

    主要代码在sql/mysqld.cc中,精简后的代码如下:
    int main(int argc, char **argv) //标准入口函数
    MY_INIT(argv[0]);//调用mysys/My_init.c->my_init(),初始化mysql内部的系统库
    logger.init_base(); //初始化日志功能
    init_common_variables(MYSQL_CONFIG_NAME,argc, argv, load_default_groups) //调用load_defaults(conf_file_name, groups, &argc, &argv),读取配置信息
    user_info = check_user(mysqld_user);//检测启动时的用户选项
    set_user(mysqld_user, user_info);//设置以该用户运行
    init_server_components();//初始化内部的一些组件,如table_cache, query_cache等。
    network_init();//初始化网络模块,创建socket监听
    start_signal_handler();// 创建pid文件
    mysql_rm_tmp_tables() || acl_init(opt_noacl)//删除tmp_table并初始化数据库级别的权限。
    init_status_vars(); // 初始化mysql中的status变量
    start_handle_manager();//创建manager线程
    handle_connections_sockets();//主要处理函数,处理新的连接并创建新的线程处理

     

    MySQL监听连接

    主要代码在sql/mysqld.cc中(handle_connections_sockets),精简后的代码如下:
    pthread_handler_t handle_connections_sockets(void *arg __attribute__((unused))) {
        FD_SET(unix_sock,&clientFDs); // unix_socket在network_init中被打开
        while (!abort_loop) { // abort_loop是全局变量,在某些情况下被置为1表示要退出。
            readFDs=clientFDs; // 需要监听的socket
            select((int) max_used_connection,&readFDs,0,0,0); // select异步(?科学家解释下是同步还是异步)监听,当接收到??以后返回。
            new_sock = accept(sock, my_reinterpret_cast(struct sockaddr *) (&cAddr),   &length); // 接受请求
            thd= new THD; // 创建mysqld任务线程描述符,它封装了一个客户端连接请求的所有信息
            vio_tmp=vio_new(new_sock, VIO_TYPE_SOCKET, VIO_LOCALHOST); // 网络操作抽象层
            my_net_init(&thd->net,vio_tmp)); // 初始化任务线程描述符的网络操作
            create_new_thread(thd); // 创建任务线程
        }
    }

    MySQL创建连接

    主要代码在sql/mysqld.cc中(create_new_thread/create_thread_to_handle_connection),精简后的代码如下:
    static void create_new_thread(THD *thd) {
        NET *net=&thd->net;
        if (connection_count >= max_connections + 1 || abort_loop) { // 看看当前连接数是不是超过了系统配置允许的最大值,如果是就断开连接。
            close_connection(thd, ER_CON_COUNT_ERROR, 1);
            delete thd;
        }
        ++connection_count;
        thread_scheduler.add_connection(thd); // 将新连接加入到thread_scheduler的连接队列中。
    }
    而thread_scheduler转而调用create_thread_to_handle_connection,精简后的代码如下:
    void create_thread_to_handle_connection(THD *thd) {
        if (cached_thread_count > wake_thread) { //看当前工作线程缓存(thread_cache)中有否空余的线程
          thread_cache.append(thd);
          pthread_cond_signal(&COND_thread_cache); // 有的话则唤醒一个线程来用
       } else {
          threads.append(thd);
          pthread_create(&thd->real_id,&connection_attrib,   handle_one_connection,   (void*) thd))); //没有可用空闲线程则创建一个新的线程
       }
    }
    创建连接使用handle_one_connection函数,精简代码如下:
    pthread_handler_t handle_one_connection(void *arg) {
        thread_scheduler.init_new_connection_thread(); // 初始化线程预处理操作
        setup_connection_thread_globals(thd); //载入一些Session级变量
        for (;;) {
            lex_start(thd); //初始化LEX词法解析器
            login_connection(thd); // 进行连接身份验证
            prepare_new_connection_state(thd); // 初始化线程Status,即show status看到的
            do_command(thd); // 处理命令
            end_connection(thd); //没事做了关闭连接,丢入线程池
        }
    }

    MySQL执行Query

    do_command函数在sql/sql_parse.cc定义,代码如下:
    bool do_command(THD *thd) {
        NET *net= &thd->net;
        packet_length = my_net_read(net);
        packet = (char*) net->read_pos;
        command = (enum enum_server_command) (uchar) packet[0]; // 解析客户端传过来的命令类型
        dispatch_command(command, thd, packet+1, (uint) (packet_length-1));
    }

    再看dispatch_command函数在sql/sql_parse.cc定义,精简代码如下:
    bool dispatch_command(enum enum_server_command command, THD *thd, char* packet, uint packet_length) {
        NET *net = &thd->net;
        thd->command = command;
        switch (command) { //判断命令类型
            case COM_INIT_DB: ...;
            case COM_TABLE_DUMP: ...;
            case COM_CHANGE_USER: ...;
            ...
            case COM_QUERY: //如果是Query
                alloc_query(thd, packet, packet_length); //从网络数据包中读取Query并存入thd->query
                mysql_parse(thd, thd->query, thd->query_length, &end_of_stmt); //送去解析
        }
    }

    mysql_parse函数负责解析SQL(sql/sql_parse.cc),精简代码如下:
    void mysql_parse(THD *thd, const char *inBuf, uint length, const char ** found_semicolon) {
        lex_start(thd); //初始化线程解析描述符
        if (query_cache_send_result_to_client(thd, (char*) inBuf, length) <= 0) { // 看query cache中有否命中,有就直接返回结果,否则进行查找
            Parser_state parser_state(thd, inBuf, length);   
            parse_sql(thd, & parser_state, NULL); // 解析SQL语句
            mysql_execute_command(thd); // 执行
        }
    }

    终于开始执行鸟~mysql_execute_command接近3k行......,非常精简代码如下:
    int mysql_execute_command(THD *thd) {
        LEX  *lex= thd->lex;  // 解析过后的SQL语句的语法结构
        TABLE_LIST *all_tables = lex->query_tables;   // 该语句要访问的表的列表
        switch (lex->sql_command) {
            ...
            case SQLCOM_INSERT:
                insert_precheck(thd, all_tables);
                mysql_insert(thd, all_tables, lex->field_list, lex->many_values, lex->update_list, lex->value_list, lex->duplicates, lex->ignore);
                break; ...
            case SQLCOM_SELECT:
                check_table_access(thd, lex->exchange ? SELECT_ACL | FILE_ACL :  SELECT_ACL,  all_tables, UINT_MAX, FALSE);    // 检查用户对数据表的访问权限
                execute_sqlcom_select(thd, all_tables);     // 执行select语句
                break;
        }
    }

    我们看一个例子, mysql_insert (在sql/sql_insert.cc),精简代码如下:
    bool mysql_insert(THD *thd,
                       TABLE_LIST *table_list,      // 该INSERT要用到的表
                       List<Item> &fields,             // 使用的项
                       ....) {
        open_and_lock_tables(thd, table_list); // 这里的锁只是防止表结构修改
        mysql_prepare_insert(...);
        foreach value in values_list {
            write_record(...);
        }
    } //里面还有很多处理trigger,错误,view之类的杂七杂八的东西,我们都忽略。

    我们接着看真正写数据的函数write_record (在sql/sql_insert.cc),精简代码如下:
    int write_record(THD *thd, TABLE *table,COPY_INFO *info) {  // 写数据记录
        if (info->handle_duplicates == DUP_REPLACE || info->handle_duplicates == DUP_UPDATE) { //如果是REPLACE或UPDATE则替换数据
            table->file->ha_write_row(table->record[0]);
            table->file->ha_update_row(table->record[1], table->record[0]));
        } else {
            table->file->ha_write_row(table->record[0]);
        }
    }

    int handler::ha_write_row(uchar *buf) { //这是啥? Handler API !
        write_row(buf);   // 调用具体的实现
        binlog_log_row(table, 0, buf, log_func));  // 写binlog
    }

  • 相关阅读:
    使用MobaXterm远程连接Ubuntu,启动Octave,界面不能正常显示
    ABP .Net Core 日志组件集成使用NLog
    ABP .Net Core Entity Framework迁移使用MySql数据库
    ABP前端使用阿里云angular2 UI框架NG-ZORRO分享
    阿里云 Angular 2 UI框架 NG-ZORRO介绍
    Visual Studio 2019 Window Form 本地打包发布猫腻
    VS Code + NWJS(Node-Webkit)0.14.7 + SQLite3 + Angular6 构建跨平台桌面应用
    ABP .Net Core 调用异步方法抛异常A second operation started on this context before a previous asynchronous operation completed
    ABP .Net Core To Json序列化配置
    .Net EF Core数据库使用SQL server 2008 R2分页报错How to avoid the “Incorrect syntax near 'OFFSET'. Invalid usage of the option NEXT in the FETCH statement.”
  • 原文地址:https://www.cnblogs.com/johnchain/p/2766971.html
Copyright © 2011-2022 走看看