+mysql> set profiling=ON;
+mysql> show profiles; # 获取保存的所有查询的信息
+mysql> select emp_no from salaries where salary = 90930;
+mysql> show profile; # 查看最近一条SQL的执行信息
+mysql> show profile cpu, block io for query N; # 查询单个SQL的性能指标
+
+详细语法信息可以查看 SHOW PROFILE Syntax 。
+
+
+
+# 参考
+
+关于 MySQL 配置选项的处理顺序可以参考 [Command-Line Options that Affect Option-File Handling](http://dev.mysql.com/doc/refman/5.7/en/option-file-options.html) 中的内容。
+
+
+
+
+
+
+{% highlight text %}
+{% endhighlight %}
diff --git a/_drafts/2015-04-30-mysql-security_init.md b/_drafts/2015-04-30-mysql-security_init.md
new file mode 100644
index 0000000..47c28c0
--- /dev/null
+++ b/_drafts/2015-04-30-mysql-security_init.md
@@ -0,0 +1,62 @@
+---
+Date: October 19, 2013
+title: MySQL 安全设置
+layout: post
+comments: true
+language: chinese
+category: [mysql,database]
+---
+
+很多时候在部署一种产品时,安全性往往是最后考虑的,不过这也带来了很大的风险。
+
+
+
+
+## 权限控制
+
+通常数据库只需要本地网络访问,所以完全没有必要将权限开放给网络。
+
+{% highlight text %}
+mysql> GRANT ALL ON *.* TO 'user'@'%'; ← 可能会存在风险
+mysql> GRANT ALL ON *.* TO 'user'@'192.168.9.128'; ← 需要指定IP
+{% endhighlight %}
+
+一定要设置 root 密码,通常可以设置成只能本地访问;最好可以将 root 修改为其它名称。
+
+{% highlight text %}
+mysql> SET PASSWORD FOR 'root'@'localhost' = PASSWORD('new-passowrd');
+mysql> UPDATE mysql.user SET user='foobar' WHERE user='root';
+mysql> FLUSH PRIVILEGES;
+{% endhighlight %}
+
+对于系统文件,其中数据文件最好使用 mysql:mysql 用户,其中需要确保只有 mysql 和 root 可以访问;对于二进制文件,同样需要设置。
+
+
+
+
+## 禁用 LOCAL INFILE
+
+用于防止非授权用户访问本地文件,可以通过如下方式查看。
+
+{% highlight text %}
+mysql> LOAD DATA LOCAL INFILE '/etc/passwd' INTO TABLE tbl;
+mysql> SELECT load_file('/etc/passwd');
+{% endhighlight %}
+
+此时,需要在配置文件中添加如下内容。
+
+{% highlight text %}
+[mysqld]
+set-variable=local-infile=0
+{% endhighlight %}
+
+
+
+
+
+
+{% highlight text %}
+{% endhighlight %}
diff --git a/_drafts/2015-05-02-mysql-skeleton_init.md b/_drafts/2015-05-02-mysql-skeleton_init.md
new file mode 100644
index 0000000..66a93c3
--- /dev/null
+++ b/_drafts/2015-05-02-mysql-skeleton_init.md
@@ -0,0 +1,642 @@
+---
+Date: October 19, 2013
+title: MySQL 代码导读
+layout: post
+comments: true
+language: chinese
+category: [mysql,database]
+keywords: mysql,database,数据库,源码,解析
+description: 在 MySQL 的官网上,MySQL 号称是 The World's Most Popular Open Source Database ,既然是开源的,据说又这么牛掰,那不看看源码真有点对不起 MySQL 了。 不禁想起了 PostgreSQL 号称是 The World's Most Advanced Open Source Database ^_^'' 废话少说,本文简单介绍一下 MySQL 的执行流程。
+---
+
+在 MySQL 的官网上,MySQL 号称是 The World's Most Popular Open Source Database ,既然是开源的,据说又这么牛掰,那不看看源码真有点对不起 MySQL 了。
+
+不禁想起了 PostgreSQL 号称是 The World's Most Advanced Open Source Database ^_^''
+
+废话少说,本文简单介绍一下 MySQL 的执行流程。
+
+
+
+![mysql skeleton]({{ site.url }}/images/databases/mysql/skeleton-logo.jpg "mysql skeleton"){: .pull-center width="50%" }
+
+## 简介
+
+MySQL 是基于线程的,在进程启动之后可以通过如下方式查看 MySQL 启动的进程信息。
+
+{% highlight text %}
+$ cat /proc/`pidof mysqld`/status | grep ^Threads ← 查看线程数
+$ cat /proc/`pidof mysqld`/sched ← 第一行即为线程数
+$ ls /proc/`pidof mysqld`/task ← 查看对应线程信息
+
+$ pstree ← 查看启动后进程之间的关系
+$ pstree -p `pidof mysqld` ← 查看进程对应的线程
+
+$ ps -Lf `pidof mysqld` ← 同样查看线程
+$ ps -eo ruser,pid,ppid,lwp,psr,args -L | grep mysql ← psr为线程运行的cpu-id
+
+$ pstack `pidof mysqld` | less ← 打印对应进程的调用堆栈
+{% endhighlight %}
+
+
+
+
+
+## 源码导读
+
+简单介绍下 MySQL 源码实现。
+
+### 简介
+
+在 MySQL 源码中,有很多类似 HAVE_XXX 的宏定义,如果是 RPM 安装包,也可以查看 mysql-xxx.devel 包中包含的 my_config.h 文件定义,也就是编译相关二进制时的宏定义。
+
+实际上,在通过 CMake 编译源码时,会以 config.h.cmake 为模板扫描系统的一些配置,并生成 config.h 文件,然后 CMake 脚本会把 config.h 拷贝一份保存为 my_config.h 文件。
+
+下面以 HAVE_SYS_EPOLL_H 宏定义为例,看下是如何实现的;在 configure.cmake 文件中,有如下的定义。
+
+{% highlight text %}
+CHECK_INCLUDE_FILES(sys/epoll.h HAVE_SYS_EPOLL_H)
+{% endhighlight %}
+
+上述文件中包含了 ```INCLUDE(CheckSymbolExists)```,而 CheckSymbolExists 是 CMake 的公共模块,一般在 cmake 的安装目录下,通常位于 /usr/share/cmake-X.X/Modules 目录下。
+
+继续研究上述的文件,在源代码中有 ```MACRO(CHECK_SYMBOL_EXISTS SYMBOL FILES VARIABLE)``` 定义;这个宏的作用就是,查找相关文件 (FILES) 里面是否包含相关符号 (SYMBOL);如果存在则设置变量为 1,MESSAGE 宏会在屏幕上做相关打印。
+
+部分宏也可以在编译时,通过类似 ```-DEMBEDDED_LIBRARY``` 定义。
+
+
+### ut_ad()宏定义
+
+在代码中,会有 ```ut_ad(dict_index_is_clust(index));``` 类似的代码,下面看看这段代码的作用。
+
+{% highlight cpp %}
+#include "os0thread.h"
+#define ut_a(EXPR) do { \
+ if (UNIV_UNLIKELY(!(ulint) (EXPR))) { \
+ ut_dbg_assertion_failed(#EXPR, \
+ __FILE__, (ulint) __LINE__); \
+ } \
+} while (0)
+
+#define ut_error \
+ ut_dbg_assertion_failed(0, __FILE__, (ulint) __LINE__)
+
+#ifdef UNIV_DEBUG
+#define ut_ad(EXPR) ut_a(EXPR)
+#define ut_d(EXPR) EXPR
+#else
+#define ut_ad(EXPR)
+#define ut_d(EXPR)
+#endif
+{% endhighlight %}
+
+也就是只有在 debug 模式下,会执行上述的代码。
+
+
+
+我们从 MySQL 启动开始,看一下 MySQL 业务流程,详细的执行流程如下。
+
+### 系统启动
+
+首先是入口函数,也就是 C/C++ 的通用入口 main(),该函数在 sql/main.cc 文件中,而实际上其最终调用的是 mysqld_main()@sql/mysqld.cc,也就是 MySQL 的真正入口函数。其详细内容如下:
+
+
+
+{% highlight text %}
+mysqld_main()
+ |-my_init() ← 做一些基本的初始化工作
+ | |-getenv() ← 设置umask,获取HOME等
+ | |-my_thread_global_ init() ← 初始化全局线程环境,包括私有数据、互斥量的初始化等
+ | |-my_thread_init() ← 分配线程内存,主要用于mysys以及dbug
+ |
+ |-load_defaults() ← 加载默认的配置项
+ |-handle_early_options() ← 做些初始参数解析,例如PS的初始化
+ | |-handle_options() ← 通用的解析命令行函数
+ |-init_sql_statement_names() ← 通过com_status_vars[]初始化,例如analyze等
+ |-sys_var_init() ← 系统变量初始化
+ | |-my_hash_init() ← 通过hash保存系统变量
+ |-adjust_related_options() ← 调整参数,如open_file_limit等
+ |-initialize_performance_schema() ← 如果需要则初始化PS
+ |-init_server_psi_keys() ← 如果需要则初始化PSI
+ |
+ |-init_error_log()
+ |-mysql_audit_initialize() ← 初始化audit全局接口,具体初始化稍后完成
+ |
+ |-init_common_variables() ← 变量的初始化
+ |-my_init_signals()
+ |-init_server_components() ← MySQL Server常用模块的初始化
+ | |-mdl_init()
+ | |-partitioning_init()
+ | |-my_timer_initialize()
+ | |-init_server_query_cache()
+ | |-randominit()
+ | |-setup_fpu()
+ | |-init_slave_list()
+ | |-open_error_log()
+ | |-transaction_cache_init()
+ | |-delegates_init()
+ | |-process_key_caches()
+ | |-ha_init_errors()
+ | |-gtid_server_init()
+ | |-plugin_init()
+ | | |-plugin_load_list()
+ | | |-plugin_dl_add() ← 包含了线程池类似插件的处理
+ | |
+ | |-ha_init()
+ | |-initialize_storage_engine()
+ | |-init_optimizer_cost_module()
+ |
+ |-init_ssl()
+ |-network_init() ← 初始化网络模块,包括初始化调度器,创建socket监听端口
+ |
+ |-init_status_vars()
+ |
+ |-connection_event_loop() ← 管理、创建新连接,会是一个死循环
+ | |-listen_for_connection_event()
+ | | |-poll()
+ | |-process_new_connection()
+ | |-add_connection()
+ | |-mysql_thread_create() ← 根据thread_handling参数选择具体方法
+ |
+ |-my_thread_join()
+ |-clean_up()
+ |-mysqld_exit() ← 程序退出
+{% endhighlight %}
+
+### 初始化网络配置
+
+网络配置其实比较简单,就是设置端口,创建套接字,绑定端口,监听端口,实现全部集中在 network_init() 函数中,下面直接给出相应的伪代码:
+
+{% highlight text %}
+network_init()
+ |-set_ports() ← 设置端口号,#define MYSQL_PORT 3306
+ |-Mysqld_socket_listener() ← 根据参数等,启动实例
+ |-init_connection_acceptor()
+ |-setup_listener() ← 不同类型listerner调用接口不同,如socket、pipe、share_memory
+ |-tcp_socket() ← 创建tcp_socket实例
+ |-get_listener_socket() ← 创建监听socket,并准备接收连接
+ |-create_lockfile()
+ |-mysql_socket_socket()
+ | |-inline_mysql_socket_socket()
+ | |-socket() ← 创建套接字
+ |
+ |-mysql_socket_bind()
+ | |-inline_mysql_socket_bind()
+ | |-bind() ← 绑定端口号
+ |
+ |-mysql_socket_listen()
+ |-inline_mysql_socket_listen()
+ |-listen() ← 监听端口号
+{% endhighlight %}
+
+客户端与服务端通信的方式不止是 SOCKET 一种,MySQL 还支持三种连接方式:namepipe、unix socket 和 shared memory,即命名管道、unix 套接字和共享内存的方式,这三种方式是可以共存的,只是有些只支持本地,socket 是最通用的方式。
+
+
+### 管理/创建新连接 !!!!
+
+通过 connection_event_loop() 实现,而且 socket 管理其实比较简单,下面是其简单的处理代码:
+
+{% highlight text %}
+connection_event_loop() ← 对应死循环,不断判断abort_loop参数
+ |-get_instance() ← 获取连接处理的实例
+ |-listen_for_connection_event()
+ | |-poll()/select() ← 监视socket文件描述符
+ | |-mysql_socket_accept() ← 处理到来的客户端连接
+ | |-Channel_info_tcpip_socket() ← 创建一个实例
+ |
+ |-process_new_connection()
+ |-add_connection() ← 创建一个新的线程,不同方式会有不同处理方式
+ |-mysql_thread_create()
+ |-pthread_create() ← 对应的处理函数是handle_connection()
+{% endhighlight %}
+
+主要处理函数,一系列异常保护之后会停止在 select()/poll() 函数处,等待接受到新的连接,如果监控到有连接,则通过 accept() 函数接受客户端的连接。
+
+
+,然后新建一个 THD 类,将连接参数全部设置到 THD 类的参数上,最后调用 create_new_thread() 函数,这个函数便是重点。
+
+mysql 为每个连接设置一个线程,而 oracle 同时也可以将请求放入一个队列当中。
+
+
+接着是创建线程来处理客户端发送来的请求,通过 create_new_thread()@sql/mysqld.cc 实现,该函数执行的主要流程如下:
+
+{% highlight c %}
+static void create_new_thread(THD *thd) {
+ ++*thd->scheduler->connection_count; // 全局连接数自增
+ thread_count++; // 全局线程数自增
+
+ // 真正创建线程,实际调用的是 thd->scheduler.add_connection(thd);
+ MYSQL_CALLBACK(thd->scheduler, add_connection, (thd));
+}
+{% endhighlight %}
+
+在创建链接时,会对当前连接数检测 connection_count,先对互斥量 LOCK_connection_count 加锁,如果大于 max_connections+1,则报错,没有问题,才新建线程,一个典型的互斥线程。此时,全局连接数+1,全局线程数+1,然后调用 add_connection() 函数,现在线程创建成功了。
+
+在 create_new_thread(thd) 的末尾,有一行代码,也就是如下的宏定义:
+
+{% highlight c %}
+MYSQL_CALLBACK(thd->scheduler, add_connection, (thd)); sql/sql_callback.h
+
+#define MYSQL_CALLBACK(OBJ, FUNC, PARAMS) \
+ do { \
+ if ((OBJ) && ((OBJ)->FUNC)) \
+ (OBJ)->FUNC PARAMS; \
+ } while (0)
+{% endhighlight %}
+
+这样,这个代码就是调用 thd->scheduler 的 add_connection 函数,参数是 (thd) 。这个函数就是我们在上面第一步设置连接的线程数中,one_thread_scheduler 和 one_thread_per_connection_scheduler 中设置的一个参数。这两者的区别便是是否创建了一个新的线程来处理到来的连接。
+
+thd->scheduler 在 THD::THD() 构建函数中初始化,该值将继承全局的 thread_scheduler 。
+
+
+## 链接处理
+
+在此,根据不同的链接方式会调用不同的接口,现在 MariaDB 支持三种处理方式。one_thread_scheduler 是单线程方式,也就是不会去新建线程,而线程池实现方式有些复杂,以后再详细了解。
+
+所以,在此,重点研究 one_thread_per_connection_scheduler 链接方式,也就是说设置的 add_connection 函数实际最终调用的是 create_thread_to_handle_connection()。
+
+void create_thread_to_handle_connection(THD *thd)@sql/mysqld.cc,在该函数中,如果设置了线程缓存,且缓存中有空闲的线程,则直接从栈中取出一个线程即可。
+
+{% highlight c %}
+create_thread_to_handle_connection(THD *thd)
+{
+ if (cached_thread_count > wake_thread)
+ thread_cache.push_back(thd);
+ else
+ thread_created++;
+ threads.append(thd); // 创建线程数自增,并加入到threads链表上
+ mysql_thread_create(key_thread_one_connection,
+ &thd->real_id,&connection_attrib,
+ handle_one_connection,
+ (void*)thd) ; // 这就是真正创建线程的地方了
+}
+{% endhighlight %}
+
+可见,最后调用了 mysql_thread_create() 函数,这是一个封装之后的函数,用于跨平台调用,对于 Linux,最后实际是通过 pthread_create() 创建了一个新的线程,而新线程的 处理函数为 handle_one_connection()。
+
+
+### 新线程处理流程
+
+新线程处理函数为 void *handle_connection(void *arg),到此为止,一个新的 connection 被一个新创建的线程所单独处理,我们看下其中是如何进行处理的。
+
+{% highlight c %}
+// 连接处理函数,入参是连接对象Channel_info
+void *handle_connection(void *arg)
+{
+ my_thread_init() // 初始化线程
+ for (;;) {
+ THD *thd= init_new_thd(channel_info); // 新建一个线程对象
+ thd_manager->add_thd(thd); // 添加到线程管理
+
+ if (thd_prepare_connection(thd)) // 包括用户认证
+ handler_manager->inc_aborted_connects();
+ else
+ {
+ while (thd_connection_alive(thd))
+ {
+ if (do_command(thd)) // 处理命令
+ break;
+ }
+ end_connection(thd);
+ }
+ close_connection(thd, 0, false, false);
+
+ thd->get_stmt_da()->reset_diagnostics_area();
+ thd->release_resources();
+ }
+}
+{% endhighlight %}
+
+在新建完线程之后,会先调用 my_thread_init() 做线程的初始化,到目前为止,才算创建了一个新的线程,接着会有一些初始化的工作。
+
+**注意,在此新建完线程后,后续的很多操作都会携带上该线程对象指针。**
+
+接着会通过 thd_prepare_connection() 函数进行一些登陆认证等操作,通过 login_connection() 函数实现,还有一些其它的初始化工作。
+
+接下来主要执行工作是在 do_command() 函数,也就是主要的命令处理函数。
+
+### 命令分发
+
+接下来是主要的命令处理函数 ```bool do_command(THD *thd)@sql/sql_parse.cc```,该函数主要用来接收、解析、执行命令报文;在线程中,该函数会不断循环执行。
+
+{% highlight c %}
+bool do_command(THD *thd)
+{
+ thd->m_server_idle= true;
+ // 如下的命令会阻塞在网络读取,直到读取了最新的报文
+ rc= thd->get_protocol()->get_command(&com_data, &command);
+ thd->m_server_idle= false;
+
+ // 接下来准备分发命令
+ return_value= dispatch_command(thd, &com_data, command);
+}
+{% endhighlight %}
+
+当客户端通过 TCP 连接上 MySQL 的服务器后,在发送请求之前,服务端的线程实际上是阻塞在 do_command() 函数中,也就是 socket 里的 read()。当接收到报文后,该函数同时还会作一些处理,如去除头部等。
+
+
+
+**需要注意的是**,有的命令只需要在 dispatch_command() 执行,例如 COM_REGISTER_SLAVE;而部分则会在 mysql_execute_command() 中执行,例如 SQLCOM_CHANGE_MASTER 。
+
+在 dispatch_command() 函数中,其主要的处理流程如下。
+
+{% highlight c %}
+bool dispatch_command(enum enum_server_command command, THD *thd, char* packet, uint packet_length)
+{
+ switch (command) {
+ case COM_INIT_DB: ... ...;
+ case COM_QUERY: {
+ if (alloc_query(thd, com_data->com_query.query,
+ com_data->com_query.length))
+ break; // fatal error is set
+
+ Parser_state parser_state;
+ if (parser_state.init(thd, thd->query().str, thd->query().length))
+ break;
+ // 开始进行SQL解析
+ mysql_parse(thd, &parser_state);
+
+ // 如果SQL中有通过分号分割的多条语句,同时会在下面处理,在此不赘述
+ }
+ }
+}
+{% endhighlight %}
+
+
+在该函数中,其主要作用的是一个巨大的 switch 语句,涵盖了 MySQL 支持的所有语句,包括了查询、PING、QUIT等指令,这些命令会在 include/my_command.h 中定义:
+
+{% highlight c %}
+enum enum_server_command
+{
+ COM_SLEEP, COM_QUIT, COM_INIT_DB, COM_QUERY, COM_FIELD_LIST,
+ ... ...
+ COM_END
+};
+{% endhighlight %}
+
+接下来命令的处理,就是根据不同的请求通过 switch 进入不同的函数入口,对于查询命令最后进入的是 COM_QUERY,先做一些初始化、写日志等后进入 ```mysql_parse()@sql/sql_parse.cc```,该函数是 SQL 语句解析的总入口。
+
+### 命令解析
+
+SQL 的解析包括了:词法分析,语法分析,语义分析,构造执行树,生成执行计划,计划的执行。SQL92 是最新的标准,里面的定义都是一些巴科斯范式(BNF),就是一种语法定义的标准。
+
+MySQL 通过 YACC(Yet Another Compiler Compiler) 进行语法解析,不过没有采用 LEX 进行词法分析,YACC 接收来自词法分析阶段分解出来的 token 然后去匹配那些 BNF 。
+
+另外,比较不错的嵌入式数据库 SQLite,词法分析器是手工写的,语法分析器由 Lemon 生成,如果感兴趣可以看下代码,在此就不详述了。
+
+在 sql/sql_yacc.cc 源码中,有如下的定义;其中词法解析相关的主要处理函数在 sql/sql_lex.cc 文件中,其入口即 MYSQLlex() ,而主要的分词处理函数为 lex_one_token() 。
+
+{% highlight c %}
+#define yyparse MYSQLparse
+#define yylex MYSQLlex
+{% endhighlight %}
+
+#### 词法解析
+
+可以直接通过 state_map[] 获得对应的状态,该数组在 init_state_maps() 中初始化,首先会将字符设置为 MY_LEX_IDENT 、数字设置为 MY_LEX_NUMBER_IDENT、空白字符设置为 MY_LEX_SKIP、其它的设置为 MY_LEX_CHAR ,然后会将一些特殊字符初始化。
+
+而关于字符的判断如下,其中 s 为对应的字符集,c 对应的序号,也就是通过 _MY_X 进行判断。
+
+{% highlight c %}
+#define my_isalpha(s, c) (((s)->ctype+1)[(uchar) (c)] & (_MY_U | _MY_L))
+{% endhighlight %}
+
+每个字符集都会对应一个 ctype ,会通过该数组判断其类型。在 sql/lex.h 中定义了关键字,用两个数组存储 static SYMBOL symbols[] 和 static SYMBOL sql_functions[]。
+
+#### SQL解析
+
+仍回到如上的函数入口。
+
+SQL 命令解析的入口是 mysql_parse(); sql/sql_parse.cc,如上所述 SQL 的语法/语义解析是通过 yacc 实现,规则文件是 sql/sql_yacc.yy 。
+
+{% highlight c %}
+void mysql_parse(THD *thd, char *rawbuf, uint length, Parser_state *parser_state)
+{
+ mysql_reset_thd_for_next_command(thd); // 重置结构体
+ lex_start(thd); // 初始化词法分析结构体
+
+ if (query_cache_send_result_to_client(...) <= 0) { // 在cache中查询
+ err= parse_sql(thd, parser_state, NULL); // 不在cache中,直接查询
+ error= mysql_execute_command(thd); // 解析完后开始执行SQL
+ } else { // 命中cache,直接返回
+ hd->lex->sql_command= SQLCOM_SELECT; // 设置结果,更新统计
+ ... ...
+ }
+}
+{% endhighlight %}
+
+在 mysql_parse() 中有段注释,大概的意思是:本来应该先调用 query_cache_send_result_to_client(),也即在 query_cache 中查询该语句,加快查询速度。失败才调用 lex_start() 和 mysql_reset_thd_for_next_command() 来初始化 thd 解析 sql。但是查询 cache 也需要干净的 thd,只能先调用 lex_start() 和 mysql_reset_thd_for_next_command() 来初始化 thd 了,这样导致代码和逻辑有悖。
+
+首先是初始化以及重置操作,接着会在 cache 中查询,如果有相同的语句,则立即从 cache 返回结果,于是整个 sql 就结束了。
+
+如果 cache 里不存在该 sql,则继续前进来到 parse_sql()@sql/sql_parse.cc,这个函数主要就是调用了 MYSQLparse(),而 MYSQLparse() 其实就是 bison/yacc 里的 yyparse。
+
+下面就开始解析 sql 了,主要是关于词法分析和语法匹配,对于一条像 select * from test 的语句首先进入词法分析,此时会找到 2 个 token(select, from),然后根据 token 进行语法匹配,规则在 sql/sql_yacc.yy 里。
+
+最后的解析结果中,lex->sql_command 保存了相应的命令。
+
+sql 解析完了,然后是一些优化操作等,接着进入 mysql_execute_command()@sql/sql_ parse.cc 函数,这个函数是所有 sql 命令执行的总入口。
+
+
+### 命令执行
+
+{% highlight c %}
+int mysql_execute_command(THD *thd)
+{
+ switch (lex->sql_command) {
+ case SQLCOM_SHOW_EVENTS: ...;
+ case SQLCOM_SELECT: {
+ check_table_access(...);
+ res= execute_sqlcom_select(thd, all_tables); // 执行查询
+ }
+ }
+}
+{% endhighlight %}
+
+在 mysql_execute_command() 中,先确定 command 要对哪张表操作 lex->first_lists_tables_same(); 根据该表的状态,会做一些预处理,尽量减少之后的操作对表的影响(因为目前还不知道这条指令执行之后,会对数据库产生什么样的影响)做好保护是必须的。
+
+然后有个 switch 语句,他决定了 command 属于哪种类型,这些类型定义在 sql/sql_ lex.h 中:
+
+{% highlight c %}
+enum enum_sql_command {
+ SQLCOM_SELECT, SQLCOM_CREATE_TABLE, ...... SQLCOM_END
+};
+{% endhighlight %}
+
+仍然以查询命令为例,最后会进入 SQLCOM_SELECT 这个 case 分支。之后就是命令的解析,处理,以及然后查询,规整结果集。
+
+最后 select 的执行,通过 execute_sqlcom_select()@sql/sql_parse.cc 实现,在 execute_sqlcom_select() 函数中,调用 handle_select() (优化入口),然后调用 mysql_select()。
+
+mysql_select() 就是执行模块,这个模块代码比较复杂,可以清楚看到创建优化器 (JOIN::prepare)、优化 (JOIN::optimize)、执行 (JOIN::exec) 的3个步骤,在 MySQL 中,会将任何 select 都转换为 JOIN 来处理的。
+
+MySQL 在设计时,采用了这样的思路:针对主要应用场景选择一个或几个性能优异的核心算法作为引擎,然后努力将一些非主要应用场景作为该算法的特例或变种植入到引擎当中。具体而言,MySQL 的 select 查询中,核心功能就是 JOIN 查询,因此在设计时,核心实现 JOIN 功能,对于其它功能,都通过转换为 JOIN 来实现。
+
+即使对于最简单的 select name from student 也会转换为 JOIN 来操作。
+
+{% highlight c %}
+if (!(join= new JOIN(thd, fields, select_options, result)))
+ ...
+if ((err= join->optimize()))
+ ...
+join->exec();
+{% endhighlight %}
+
+结束了优化,我们要具体执行 join->exec(),该函数实际进入的是 JOIN::exec()@sql_select.cc。
+
+exec()首先向客户端发送字段title的函数send_result_set_metadata(),没数据但字段也是要的。然后再进入 do_select() ,根据表的存储引擎跳入到引擎具体的实现。如果是 myisam,则通过 myisam 引擎扫描文件,其中 info->filename 实际保存的是文件的地址。
+
+最后通过 join->result->send_data() 将数据发送给用户。并从 dispatch_command() 返回,最后在 net_end_statement 结束整个 sql 。
+
+
+## 总结
+
+处理 MySQL 客户端命令,在此以 one_thread_per_connection_scheduler 方式为例,也就是创建 handle_one_connection() 独立线程处理请求。
+
+{% highlight text %}
+handle_connections_sockets()
+ |-poll() 通过gdb查看,可以看到在此等待连接
+ |-thd = new THD; my_net_init()
+ |-create_new_thread() 根据不同的thread handler调用不同的函数
+ |-create_thread_to_handle_connection() one_thread_per_connection_scheduler方式
+ |-handle_one_connection() 创建的新线程来处理
+ |-do_handle_one_connection()
+ |-do_command() 在死循环中处理
+ |-my_net_read_packet()
+ |-dispatch_command() 一堆的switch,根据客户端报文类型解析,include/mysql_com.h
+++=== SQL Interface ==|+++|
+ | |-mysql_change_db() 执行use db命令,COM_INIT_DB
+ | |-sql_kill() 执行kill命令,COM_PROCESS_KILL
+ | |- ... ...
+ | |-mysql_parse() 执行SQL语句,COM_QUERY
+ | |-lex_start()
+ | |-mysql_reset_thd_for_next_command()
+ | |-query_cache_send_result_to_client()
+ | |-parse_sql()
+ | | |-MYSQLparse() 通过yacc解析SQL,规则文件保存在sql/sql_yacc.yy
+ | |
+ | | 各种类型的SQL,一个大switch语句
+ | |-mysql_execute_command() 根据不同的SQL语句执行,sql/sql_cmd.h,对item调试
+ | |-execute_show_status() 执行show status,SQLCOM_SHOW_STATUS
+ | |- ... ...
+ | |-check_table_access() 执行select,SQLCOM_SELECT
+ | |-execute_sqlcom_select()
+ | | |-open_and_lock_tables()
+ | | | |-open_tables()
+ | | | | |-open_and_process_table()
+ | | | | |-open_table()
+ | | | | |-Table_cache::get_table()
+ | | | | |-get_table_share_with_discover()
+ | | | | | |-get_table_share()
+ | | | | | |-open_table_def()
+ | | | | | |-my_open()
+ | | | | | |-open_binary_frm()
+ | | | | | |-get_new_handler() 获取表的handler
+ | | | | |-my_malloc // 申请表数据结构
+ | | | | |-open_table_from_share
+ | | | | |-handler::ha_open
+ | | | | |-ha_innobase::open
+ | | | | |-dict_table_open_on_name
+ | | | | |-dict_load_table
+ | | | | |-btr_pcur_is_on_user_rec
+ | | | | |-dict_load_table_low
+ | | | | | |-dict_mem_table_create
+ | | | | |-fil_space_for_table_exists_in_mem
+ | | | | |-fil_open_single_table_tablespace // 打开表空间文件
+ | | | |-lock_tables()
+ | | | |-mysql_handle_derived()
+ | | |-query_cache_store_query() 先查看缓存
+ | | |
+ | | |-handle_select() SQL处理的真正入口,会判断是否为union
+ | | |-mysql_union() 如果含有union,则调用该函数
+ | | |-mysql_select() 否则调用该函数
+++=== Query Parser ===|++ | |
+ | | |-mysql_prepare_select()
+ | | | |-JOIN::prepare()@sql/sql_select.cc
+ | | | | |-setup_tables_and_check_access()
+ | | | | |-setup_wild()
+ | | | | |-setup_fields()
+ | | | | |-setup_without_group()
+ | | | | |-setup_order() order by语句相关
+ | | | |-find_order_in_list()
+ | | | |-find_item_in_list()
+ | | |
+ | | |-lock_tables()
+ | | |-query_cache_store_query()
+ | | |-mysql_execute_select()
+ | | |
+++=== Query Prepare ==|+++ | |
+ | | |-JOIN::optimize() @sql/sql_optimizer.cc
+ | | |
+ | | |
+ | | |-JOIN::explain() @sql/sql_explain.cc
+ | | | | 如果使用的是explain语句,返回而不执行
+ | | | |-prepare_result()
+ | | | |-explain_query_specification()
+ | | |
+ | Explain_query::send_explain()
+++=== Query Optimizer |==+++ | |
+ | | |-JOIN::exec() 根据执行计划进行相应处理
+ | | |-exec_inner()
+ | | |-select_result::prepare()
+ | | |-select_result::prepare2()
+ | | |-select_send::send_result_set_metadata()
+ | | | |-Protocol::send_result_set_metadata()
+ | | |
+ | | |-do_select() 查询入口函数
+ | | |-join->first_select() 1. 实际调用sub_select(),也即循环调用
+ | | rnd_next()+evaluate_join_record()
+ | | | |
+ | | | | while循环读取数据
+ | | | |-join_tab->read_first_record() 首次调用,实际为init_read_record()
+ | | | | |-ha_rnd_init()
+ | | | | | |-change_active_index()
+ | | | | | |-innobase_get_index()
+ | | | | |-innobase_trx_init()
+ | | | |-info->read_record() 再次调用,该函数在init中初始化
+ | | | |
+ | | | |-evaluate_join_record() 处理一条查询记录
+ | | | |-end_send()
+ | | | |-select_send::send_data()
+ | | | |-Protocol::write()
+ | | |
+ | | |-join->result->send_eof()
+++=== Query Execution |==+++ | |
+ st_select_lex::cleanup |
+ | |
+ | |
+ | |-update_precheck()
+ | |-mysql_update()
+ | | |-open_normal_and_derived_tables()
+ | | |-mysql_prepare_update()
+ | | |-innobase_register_trx()
+ | | |-innobase_register_trx()
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ | |
+ |
+ |-thd->protocol->end_statement() 将获得的查询结果发送到客户端
+{% endhighlight %}
+
+在查询记录时,会循环调用 ha_innobase::rnd_next() 和 evaluate_join_record() 获取并处理该部分的每条记录。
+
+
+### 结论
+
+整个 connection manager 的流程十分清晰,单线程的连接一般很少使用,大多使用多线程方式。多线程连接中其实还涉及到线程缓冲池的概念,即如果一个连接断开后,其所创建的线程不会被销毁掉,而是放到缓冲池中,等待下一个新的 connection 到来时,首先去线程缓冲池查找是否有空闲的线程,有的话直接使用,木有的话才去创建新的线程来管理这个 connection。
+
+
+{% highlight text %}
+{% endhighlight %}
diff --git a/_drafts/2015-05-18-mysql-connection_init.md b/_drafts/2015-05-18-mysql-connection_init.md
new file mode 100644
index 0000000..7010907
--- /dev/null
+++ b/_drafts/2015-05-18-mysql-connection_init.md
@@ -0,0 +1,335 @@
+---
+title: MySQL 链接方式
+layout: post
+comments: true
+language: chinese
+category: [mysql,database]
+keywords: mysql,连接,connection
+description: 与 Oracle 或者 Postgre 不同,MySQL 采用的是线程模型,在这里介绍通过 socket 链接到服务器之后,线程与链接直接是怎么处理的。
+---
+
+与 Oracle 或者 Postgre 不同,MySQL 采用的是线程模型,在这里介绍通过 socket 链接到服务器之后,线程与链接直接是怎么处理的。
+
+
+
+## 简介
+
+现在 MySQL 支持三种处理链接的方式:no-threads、one-thread-per-connection 和 pool-of-threads,默认使用 one-thread-per-connection。而线程池的方式,只有企业版才支持;单线程则通常用户调试或者嵌入式的模式,因此,在此主要介绍每个连接单线程的方式。
+
+在启动时可以通过 \-\-thread-handling=XXX 参数指定,也可以在配置文件中指定,而当前使用的链接方式可以通过 ```show variables like 'thread_handling'``` 查看。注意,该选项是只读的,也就是链接方式只能在启动时进行设置。
+
+MariaDB 在 5.5 引入了一个动态的线程池方案,可以根据当前请求的并发情况自动增加或减少线程数,在此的线程池就是 MariaDB 的解决方案。
+
+1. 单线程 在同一时刻,最多只能有一个链接连接到 MySQL ,其他的连接会被挂起,一般用于实验性质或者嵌入式应用。
+
+2. 多线程
+ 同一时刻可以支持多个链接同时连接到服务器,针对每个链接分配一个线程来处理这个链接的所有请求,直到连接断开,线程才会结束。
+ 这种方式存在的问题就是需要为每个连接创建一个新的 thread,当并发连接数达到一定程度,性能会有明显下降,因为过多的线程会导致频繁的上下文切换,CPU cache 命中率降低和锁的竞争会更加激烈。
+
+3. 线程池 解决多线程的方法就是降低线程数,这样就需要多个连接共用线程,这便引入了线程池的概念。线程池中的线程是针对请求的,而不是针对连接的,也就是说几个连接可能使用相同的线程处理各自的请求。
+
+注意,后面主要介绍 MariaDB 的实现方式。
+
+### 设置
+
+如上所述,可以通过设置服务器的启动参数来设定连接的方式,通过 mysqld \-\-verbose \-\-help 命令可以查看所支持的选项,启动后可以通过 show status 查看与行状态,通过 show variables 查看启动时的变量。
+
+{% highlight text %}
+$ mysqld --thread-handling=no-threads/one-thread-per-connection/pool-of-threads
+
+$ cat /etc/my.cnf
+[mysqld]
+thread_handling=pool-of-threads
+
+mysql> SHOW VARIABLES LIKE 'thread_handling'; ← 查看连接配置
++-----------------+---------------------------+
+| Variable_name | Value |
++-----------------+---------------------------+
+| thread_handling | one-thread-per-connection |
++-----------------+---------------------------+
+1 row in set (0.01 sec)
+{% endhighlight %}
+
+除了上述的链接方式之外,为了防止链接过多,导致在管理时无法登陆,MariaDB 提供了额外的链接方式,可以通过设置如下的参数实现 \-\-extra-port=3308 \-\-extra-max-connections=1 。
+
+注意,如果 extra-max-connections 设置为 2 则实际上可以创建三个链接,而且只支持 one-thread-per-connection 类似的方式。
+
+### 监控状态
+
+MySQL 启动后,会监听端口,当有新的客户端发起连接请求时,MySQL 将为其分配一个新的 thread,去处理此请求。从建立连接开始,CPU 要给它划分一定的 thread stack,然后进行用户身份认证,建立上下文信息,最后请求完成,关闭连接,释放资源。
+
+高并发情况下,将给系统带来巨大的压力,不能保证性能。MySQL 通过线程缓存来是实现线程重用,减小这部分的消耗;一个连接断开,并不销毁承载其的线程,而是将此线程放入线程缓冲区,并处于挂起状态,当下一个新的连接到来时,首先去线程缓冲区去查找是否有空闲的线程,如果有,则使用之,如果没有则新建线程。
+
+{% highlight text %}
+mysql> SHOW VARIABLES LIKE 'thread_cache_size'; ← 可以重用线程的个数
++-------------------+-------+
+| Variable_name | Value |
++-------------------+-------+
+| thread_cache_size | 9 |
++-------------------+-------+
+1 row in set (0.03 sec)
+
+mysql> SHOW STATUS LIKE 'threads%'; ← 查看状态
++-------------------+-------+
+| Variable_name | Value |
++-------------------+-------+
+| Threads_cached | 0 | ← 已被线程缓存池缓存的线程个数
+| Threads_connected | 2 | ← 当前MySQL的连接数
+| Threads_created | 1065 | ← 已创建线程个数,可用来判断thread_cache_size大小
+| Threads_running | 1 | ← 正在运行的线程数
++-------------------+-------+
+4 rows in set (0.13 sec)
+{% endhighlight %}
+
+
+
+
+## 源码导读
+
+首先大致介绍线程管理。
+
+### 数据结构
+
+MariaDB 支持的链接类型可以通过 enum scheduler_types 查看,目前只支持上述的三种类型。根据启动时的配置项,会将所使用的链接方式会保存在 scheduler_functions thread_scheduler 变量中。
+
+{% highlight c %}
+struct scheduler_functions
+{
+ uint max_threads, *connection_count;
+ ulong *max_connections;
+ bool (*init)(void);
+ bool (*init_new_connection_thread)(void);
+ void (*add_connection)(THD *thd);
+ void (*thd_wait_begin)(THD *thd, int wait_type);
+ void (*thd_wait_end)(THD *thd);
+ void (*post_kill_notification)(THD *thd);
+ bool (*end_thread)(THD *thd, bool cache_thread);
+ void (*end)(void);
+};
+{% endhighlight %}
+
+在初始化时,会通过如下函数设置全局变量 thread_scheduler 的值。
+
+{% highlight text %}
+mysqld_main()
+ |-init_common_variables()
+ |-get_options()
+{% endhighlight %}
+
+该选项会在 mysqld_get_one_option()@sql/mysqld.cc 中处理,这个是在解析参数时调用的,相关的部分如下:
+
+{% highlight c %}
+case OPT_ONE_THREAD:
+ thread_handling = SCHEDULER_NO_THREADS;
+ break;
+{% endhighlight %}
+
+在主函数中,调用 logger.init_base() 之后,调用了一个 init_common_variables() 函数,里面初始化了这个变量的值。在 init_common_variables() 中调用了一个 get_options(); sql/mysqld.cc 函数,在该函数中对全局变量 thread_handling 进行初始化,然后设置相应的模式:
+
+{% highlight c %}
+if (thread_handling <= SCHEDULER_ONE_THREAD_PER_CONNECTION)
+ one_thread_per_connection_scheduler(thread_scheduler, &max_connections,
+ &connection_count);
+else if (thread_handling == SCHEDULER_NO_THREADS)
+ one_thread_scheduler(thread_scheduler);
+else
+ pool_of_threads_scheduler(thread_scheduler, &max_connections,
+ &connection_count);
+{% endhighlight %}
+
+在这三个函数中,其时就是设置了一个类型为 struct scheduler_functions 的 thread_scheduler 变量,只不过这些参数是一些函数指针罢了,也就是说在具体的调用中,只需要调用 add_connection 或 end_thread 即可,不需要知道到底是调用了哪个函数。
+
+
+
+
+## 线程池
+
+对于为每个连接创建一个线程的方式,当并发连接数与可以提供服务的 CPU 数的比例达到一定程度后,性能会有明显下降。这是因为过多的线程会导致较多的内存消耗、频繁的上下文切换以及 CPU cache 命中率下降;同时在访问临界资源(通常是用互斥量包裹的资源,用于防止不同的CPU同时修改导致错误)时会大量增加锁的竞争,这种情况对写入影响会更大。
+
+为了解决这一问题,最好是服务的线程数要小于客户端的链接数,同时希望能发挥 CPU 的最大性能,因此,通常是每个 CPU 会有一个工作线程。线程池适合短查询以及 CPU 密集型(如OLTP)。
+
+### 简介
+
+线程池的特点。
+
+1. 整个连接池内部被分成 N 个小的 group,默认为 CPU 的个数,可以通过参数 thread_pool_size 设置,group 之间通过 round robin 的方式分配连接,group 内通过竞争方式处理连接,一个 worker 线程只属于一个 group 。
+
+2. 每个group有一个动态的listener,worker线程在循环取event时,发现队列为空时会充当listener通过epoll的方式监听数据,并将监听到的event放到group中的队列
+
+3. 延时创建线程,group中的活动线程数为0或者group被阻塞时,worker线程会被创建,worker线程被创建的时间间隔会随着group内已有的线程数目的增加而变大
+
+4. worker线程,数目动态变化,这也是相对于5.1版本的一个改进,并发较大时会创建更多的worker线程,当从队列中取不到event时work线程将休眠,超过thread_pool_idel_timeout后结束生命
+
+5. timer线程,它会每隔一段时间做两件事情:1)检查每个group是否被阻塞,判定条件是:group中的队列中有event,但是自上次timer检查到现在还没有worker线程从中取出event并处理;2)kill超时连接并做一些清理工作
+
+### 配置参数
+
+线程池相关的参数都保存在 sql/sys_vars.cc 文件中,可以通过 show variables like 'thread_pool%'; 命令查看,相关的参数有:
+
+* thread_pool_size,线程池中group的数目
+
+ MariaDB 的线程池分成了不同的 group ,而且是按照到来 connection 的顺序进行分组的,如第一个 connection 分配到 group[0] ,那么第二个 connection 就分配到 group[1] ,是一种 Round Robin 的轮询分配方式,默认值是 CPU core 个数。
+
+* thread_pool_idle_timeout,线程最大空闲时间
+
+ 如果某个线程空闲的时间大于这个参数,则线程退出。
+
+* thread_pool_stall_limit,监控线程的间隔时间
+
+ thread pool 有个监控线程,每隔一段时间,会检查每个 group 的线程可用数等状态,然后进行相应的处理,如 wake up 或者 create thread 。
+
+* thread_pool_oversubscribe,允许的每个 group 上的活跃的线程数
+
+ 注意这并不是每个 group上 的最大线程数,而只是可以处理请求的线程数。
+
+* thread_pool_max_threads,最大线程数
+
+
+
+### 源码解析
+
+如上的源码中,对于线程池,会在 pool_of_threads_scheduler() 中会将全局变量 thread_scheduler 初始化为 tp_scheduler_functions 。
+
+{% highlight c %}
+static scheduler_functions tp_scheduler_functions = {
+ 0, // max_threads
+ NULL,
+ NULL,
+ tp_init, // init
+ NULL, // init_new_connection_thread
+ tp_add_connection, // add_connection
+ tp_wait_begin, // thd_wait_begin
+ tp_wait_end, // thd_wait_end
+ post_kill_notification, // post_kill_notification
+ NULL, // end_thread
+ tp_end // end
+};
+
+void pool_of_threads_scheduler(struct scheduler_functions *func,
+ ulong *arg_max_connections, uint *arg_connection_count)
+{
+ *func = tp_scheduler_functions;
+ func->max_threads= threadpool_max_threads;
+ func->max_connections= arg_max_connections;
+ func->connection_count= arg_connection_count;
+ scheduler_init();
+}
+{% endhighlight %}
+
+也就是说,根据 tp_scheduler_functions 变量中的定义,对于 thread pool 方式,这种方式对应的初始函数为 tp_init(),创建新连接的函数为 tp_add_connection(),等待开始函数为 tp_wait_begin(),等待结束函数为 tp_wait_end() 。
+
+其中 thread pool 涉及的源码在 sql/threadpool_{common,unix}.cc,其中初始化函数如下。
+
+
+### 初始化
+
+初始化的函数调用逻辑如下:
+
+{% highlight text %}
+tp_init()
+ |-scheduler_init()
+ |-thread_group_init() # 对组进行初始化
+ |-start_timer() # 开启监控线程timer_thread()
+{% endhighlight %}
+
+至此为止,thread pool 里面只有一个监控线程启动,而没有任何工作线程,直到有新的连接到来。
+
+### 处理链接
+
+当有新连接到来时,会调用 create_new_thread() 函数,而该函数实际会调用 tp_add_connection() 函数,其中比较重要的数据结构如下:
+
+{% highlight c %}
+struct connection_t {
+ THD *thd;
+ thread_group_t *thread_group;
+ connection_t *next_in_queue;
+ connection_t **prev_in_queue;
+ ulonglong abs_wait_timeout; // 等待超时时间
+ bool logged_in; // 是否进行了登录验证
+ bool bound_to_poll_descriptor; // 是否添加到了epoll进行监听
+ bool waiting; // 是否在等待状态,如I/O、sleep
+};
+
+struct thread_group_t {
+ mysql_mutex_t mutex;
+ connection_queue_t queue; // connection请求链表
+ worker_list_t waiting_threads; // group中正在等待被唤醒的thread
+ worker_thread_t *listener; // 当前group中用于监听的线程
+ pthread_attr_t *pthread_attr;
+ int pollfd; // epoll 文件描述符,用于绑定group中的所有连接
+ int thread_count; // 线程数
+ int active_thread_count;//活跃线程数
+ int connection_count; //连接数
+ /* Stats for the deadlock detection timer routine.*/
+ int io_event_count; //epoll产生的事件数
+ int queue_event_count; //工作线程消化的事件数
+ ulonglong last_thread_creation_time;
+ int shutdown_pipe[2];
+ bool shutdown;
+ bool stalled; // 工作线程是否处于停滞状态
+} MY_ALIGNED(512);
+{% endhighlight %}
+
+调用逻辑。
+
+{% highlight text %}
+tp_add_connection()
+ |-threads.append() # 首先添加到threads列表中
+ |-alloc_connection() # 申请连接
+ |-queue_put() # 放到队列中
+ |-wake_or_create_thread() # 如果没有活跃的线程,那么就无法处理这个新到的请求
+ | 这时调用该函数
+ |-wake_thread() # 首先尝试唤醒group
+{% endhighlight %}
+
+先根据 thread_id 对 group_count 取模,找到所属的 group,然后调用 queue_put() 将此 connection 放到 group 中的 queue 中。
+
+等待线程链表 waiting_threads 中的线程,如果没有等待中的线程,则需要创建一个线程。至此,新到的 connection 被挂到了 group 的 queue 上,这样一个连接算是 add 进队列了,那么如何处理这个连接呢?
+
+由于是第一个连接到来,那么肯定没有 waiting_threads,此时会调用create_worker() 创建一个工作线程,也就是 worker_main() 。
+
+{% highlight text %}
+worker_main()
+ |-get_event() # 获取要处理的连接,其中等待事件可能是登陆请求或socket中未读的字节
+ | |-queue_get() # 从队列中获取相应的连接
+ | |-listener() # 如果队列中没有监听的进程,则选择其中一个用来监听
+ |
+ |-handle_event() # 处理相应的连接
+ | |-threadpool_add_connection() # 如果没有登陆过,则调用该函数
+ | |-threadpool_process_request() # 否则已经登陆,则直接处理请求
+ | |-set_wait_timeout()
+ | |-start_io()
+ | |-mysql_socket_getfd()
+ | |-io_poll_associate_fd() # 将新到连接的socket绑定到group的epoll上进行监听
+ |
+ |-my_thread_end()
+{% endhighlight %}
+
+其中 one thread per connection 中每个线程也是一个循环体,这两者的区别是,thread pool 的循环等待的是一个可用的 event,并不局限于某个固定的 connection 的 event;而前者的循环等待是等待固定连接上的 event,这就是两者最大的区别。
+
+第一个连接到来,queue 中有了一个 connection,这时 get_event 便会从 queue 中获取到一个 connection,返回给 worker_main 线程。worker_main 接着调用 handle_event 进行事件处理。
+
+每个新的连接到服务器后,其 socket 会绑定到 group 的 epoll 中,所以,如果 queue 中没有连接,需要从 epoll 中获取,每个 group 的所有连接的 socket 都绑定在 group 的 epoll 中,所以任何一个时刻,最多只有一个线程能够监听 epoll,如果epoll 监听到有 event的话,也会返回相应的connection,然后再调用handle_event进行处理。
+
+
+当 group 中的线程没有任务执行时,所有线程都会在 get_event() 处等待,但是有两种等待方式,一种是在 epoll 上等待事件,每个 group 中只有一个线程会做这个事情,且这个会一直等待,直到有新的事件到来。
+
+另一种是等待参数 thread_pool_idle_time 指定的时间,若超过了这个时间,那么当前的线程的 get_event 就会返回空,然后 worker_main 线程就会退出。如果在线程等待的过程被唤醒的话,那么就会继续在 get_event 中进行循环,等待新的事件。
+
+
+
+
+## 其它
+
+介绍一些与线程相关的内容。
+
+### 链接池和线程池
+
+链接池是部署在应用端,为了防止客户端会频繁建立链接然后中断链接,当用户不需要该链接时,会在客户端缓存这些链接,这样如果下次用户再需要建立链接时可以直接复用该链接。
+
+链接池可以有效减小服务器和客户端的执行时间,但是不会影响查询性能。
+
+
diff --git a/_drafts/2015-05-20-mysql-memory_init.md b/_drafts/2015-05-20-mysql-memory_init.md
new file mode 100644
index 0000000..e870cd1
--- /dev/null
+++ b/_drafts/2015-05-20-mysql-memory_init.md
@@ -0,0 +1,114 @@
+---
+title: MySQL 内存配置
+layout: post
+comments: true
+language: chinese
+category: [mysql,database]
+keywords: mysql,memory,内存
+description:
+---
+
+
+
+
+![Monitor Logo]({{ site.url }}/images/databases/mysql/monitor-logo.png "Monitor Logo"){: .pull-center }
+
+
+如果应用程序使用内存过多,可能会导致操作系统通过 oom-killer 将其杀死,通常会在 syslog 日志中有所体现。MySQL 内存分为了两部分,内存使用量可以查看 [MySQL 内存计算器](http://www.mysqlcalculator.com/) 。
+
+这里的内存配置,可以直接通过 SHOW VARIABLES LIKE 'variable-name' 查看。
+
+### 全局级别
+
+
+#### key_buffer_size
+#### query_cache_size
+#### tmp_table_size
+#### innodb_buffer_pool_size
+
+InnoDB Buffer Pool 的内存大小。
+
+#### innodb_additional_mem_pool_size
+
+InnoDB 中其它内存使用。
+
+#### innodb_log_buffer_size
+
+
+
+
+
+
+
+
+
+
+### 会话级别
+
+通过 max_connections 可以设置最大连接数,然后计算单个连接最大的内存消耗,就可以计算出与会话相关的最大内存消耗。
+
+需要注意的是 read_buffer_size, sort_buffer_size, read_rnd_buffer_size, tmp_table_size 这些参数在需要的时候才分配,操作后释放;而且不管使用多少都分配该 size 的值所对应的内存数,即使实际需要远远小于这些 size。
+
+另外,每个线程可能会不止一次需要分配 buffer,例如子查询,每层都需要有自己的 read_buffer, sort_buffer, tmp_table_size 等。
+
+#### sort_buffer_size
+
+排序使用最大内存,在 Linux 下,如果超过 256KB 和 2MB 建议使用会话级变量。
+
+#### read_buffer_size
+
+#### read_rnd_buffer_size
+
+#### join_buffer_size
+
+#### thread_stack
+
+#### binlog_cache_size
+
+{% highlight text %}
+mysql> SHOW VARIABLES LIKE '%binlog_cache_size%';
++-----------------------+----------------------+
+| Variable_name | Value |
++-----------------------+----------------------+
+| binlog_cache_size | 32768 |
+| max_binlog_cache_size | 18446744073709547520 |
++-----------------------+----------------------+
+2 rows in set (0.00 sec)
+{% endhighlight %}
+
+
+
+
+
+### sort_buffer_size
+
+mysql> SHOW VARIABLES LIKE '%sort_buffer_size%';
+mysql> SET GLOBAL sort_buffer_size = 1024*1024;
+mysql> SHOW GLOBAL STATUS LIKE '%sort%';
+
+sort_buffer_size 每个会话都会分配,通用排序优化与存储引擎无关。注意: 需要最少存储15条记录。如果秒级的 SHOW GLOBAL STATUS LIKE 'Sort_merge_passes' 参数较大,可以适当增加该参数,从而优化 ORDER BY, GROUP BY 的性能。 另外,优化器可能会分配过多的内存,进而影响其它查询,所以最好针对需要较大内存的查询设置会话变量,减小影响。在 Linux 中,有 256KB 和 2MB 两个边界值,可能会导致内存申请效率降低。max_sort_length 在执行 GROUP BY, ORDER BY, DISTINCT 操作时,比较时判断多少字节;如果需要增加该值,则需要同时调整 sort_buffer_size 。innodb_sort_buffer_size ???官方文档提供了一个计算方法,没有看懂??? 创建 InnoDB 索引的时候可以使用的排序缓存大小,索引创建完成后释放,???同时也会影响到 Online DDL???。创建索引时分为了 sort+merge 两个阶段,增加该值可以减小迭代次数,从而加快索引创建速度。myisam_sort_buffer_size MyISAM 在 REPAIR TABLE(Sorting Index), CREATE INDEX(Creating Index), ALTER TABLE(Creating Index) 时使用内存数目。
+
+
+默认256K每个session 需要做一个排序分配的一个buffer,sort_buffer_size 不指定任何的存储引擎,适用于一般的方式进行优化如果你看到很多的ort_merge_passes per second你可以考虑增加sort_buffer_size 来加速ORDER BY 或者GROUP BY 操作,不能通过查询或者索引优化的。在MySQL 5.6.4 优化器尝试解决需要多少空间,但可以分配更多,达到极限。 在MySQL 5.6.4, 优化器分配整个buffer 即使如果根本不需要所有。在任何情况下, 设置它大于需要的全局会减慢很多的查询。最后是作为一个会话设置来增加,只有对需要大量的内存的会话, 在Linux上,有阀值为256KB 和2MB ,大的值可能显著的减慢内存分配,因此你应该考虑下面中的一个值。Index Condition Pushdown (EXPLAIN: Using Index Condition)首先 MySQL-Server 和 Storage-Engine 是两个组件,Server 负责 SQL 解析、优化、执行;Storage Engine 真正的负责 data+index 的读写。入入. 以前是这样: server 命令 storage engine 按 index 把相应的 数据 从 数据表读出, 传给server, server来按 where条件 做选择; 现在 ICP则是在 可能的情况下, 让storage engine 根据index 做判断, 如果不符合 条件 则无须 读 数据表. 这样 节省了disk IO. https://dev.mysql.com/doc/refman/5.6/en/index-condition-pushdown-optimization.html•不用index 因为 你 是 select *, 而且你的where 是 >=, mysql 如果用index查找 则 会有 太多的 random disk IO. 所以它选择了 全表读.如何避免全表扫描通过 EXPLAIN 查看时,如果在 type 字段中有 ALL 的话,就表示为全表扫描,通常为如下条件:• 表比较小(小于10行,行比较短),使用索引反而会浪费时间;• 在 ON 和 WHERE 子句中,没有有效的约束条件;• 区分度(cardinality) 过低,全表扫描效率可能更高;可以通过 ANALYZE TABLE tbl_name 更新,或者使用 FORCE INDEX 指定索引。SELECT * FROM t1, t2 FORCE INDEX (idx_for_column) WHERE t1.col_name=t2.col_name;另外,可以设置 max_seeks_for_key=1000 ,也就是告诉优化器,所有的 key scan 不会超过 1000 次。??????Cardinality is the count of how many items in the index are unique.Reducing max_seeks_for_key to 1,000 is like telling MySQL that you want it to use indexes when the cardinality of the index is over 1,000. I’ve seen this variable reduced to as low as 1 on some servers without any issues.https://major.io/2007/08/03/obscure-mysql-variable-explained-max_seeks_for_key/
+
+
+'Using index condition' VS. 'Using where; Using index'Using index condition : where condition contains indexed and non-indexed column and the optimizer will first resolve the indexed column and will look for the rows in the table for the other condition (index push down)Using where; Using index : 'Using index' meaning not doing the scan of entire table. 'Using where' may still do the table scan on non-indexed column but it will use if there is any indexed column in the where condition first more like using index conditionWhich is better? 'Using where; Using index' would be better then 'Using index condition' if query has index all covering.When you see Using Index in the Extra part of an explain it means that the (covering) index is adequate for the query. In your example: SELECT id FROM test WHERE id = 5; the server doesn't need to access the actual table as it can satisfy the query (you only access id) only using the index (as the explain says). In case you are not aware the PK is implemented via a unique index. When you see Using Index; Using where it means that first the index is used to retrieve the records (an actual access to the table is not needed) and then on top of this result set the filtering of the where clause is done. In this example: SELECT id FROM test WHERE id > 5; you still fetch for id from the index and then apply the greater than condition to filter out the records non matching the condition
+
+
+https://dev.mysql.com/doc/refman/5.6/en/index-condition-pushdown-optimization.html
+
+
+
+
+
+ORDER BY 优化 未完成???MySQL 中部分场景可以通过索引将排序优化消除掉。CREATE TABLE IF NOT EXISTS foobar ( id INT NOT NULL, first_name VARCHAR(30) NOT NULL, last_name VARCHAR(30) NOT NULL, address VARCHAR(150) NOT NULL, INDEX idx_name (first_name, last_name), PRIMARY KEY(id))""")CREATE TABLE IF NOT EXISTS foobar ( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, first_name VARCHAR(30) NOT NULL, last_name VARCHAR(30) NOT NULL, address VARCHAR(150) NOT NULL, INDEX idx_name (first_name, last_name));INSERT INTO foobar(first_name, last_name, address) VALUES ('Andy', 'Ddufresne', 'The Shawshank Redemption');SELECT * FROM foobar WHERE first_name='Andy' and last_name='Ddufresne';// key_part1,key_part2 前缀匹配,排序方式相同EXPLAIN SELECT * FROM foobar ORDER BY first_name,first_name LIMIT 10;EXPLAIN SELECT * FROM foobar ORDER BY first_name DESC, first_name DESC LIMIT 10;+----+-------------+--------+-------+---------------+----------+---------+------+------+-------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+--------+-------+---------------+----------+---------+------+------+-------+| 1 | SIMPLE | foobar | index | NULL | idx_name | 184 | NULL | 10 | NULL |+----+-------------+--------+-------+---------------+----------+---------+------+------+-------+1 row in set (0.00 sec)// key_part1=constant,key_part2 开始为常量 ???为什么第一个采用了Using index conditionEXPLAIN SELECT * FROM foobar WHERE first_name='Andy' ORDER BY last_name;+----+-------------+--------+------+---------------+----------+---------+-------+-------+------------------------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+--------+------+---------------+----------+---------+-------+-------+------------------------------------+| 1 | SIMPLE | foobar | ref | idx_name | idx_name | 92 | const | 19244 | Using index condition; Using where |+----+-------------+--------+------+---------------+----------+---------+-------+-------+------------------------------------+1 row in set (0.00 sec)EXPLAIN SELECT * FROM foobar WHERE first_name='Andy' ORDER BY last_name DESC;+----+-------------+--------+------+---------------+----------+---------+-------+-------+-------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+--------+------+---------------+----------+---------+-------+-------+-------------+| 1 | SIMPLE | foobar | ref | idx_name | idx_name | 92 | const | 19244 | Using where |+----+-------------+--------+------+---------------+----------+---------+-------+-------+-------------+1 row in set (0.00 sec)// key_part1 > const ???为什么采用filesortEXPLAIN SELECT * FROM foobar WHERE first_name > 'Andy' ORDER BY last_name ASC;+----+-------------+--------+------+---------------+------+---------+------+-------+-----------------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+--------+------+---------------+------+---------+------+-------+-----------------------------+| 1 | SIMPLE | foobar | ALL | idx_name | NULL | NULL | NULL | 99391 | Using where; Using filesort |+----+-------------+--------+------+---------------+------+---------+------+-------+-----------------------------+1 row in set (0.00 sec)// key_part1 < const ???为什么采用filesortEXPLAIN SELECT * FROM foobar WHERE first_name < 'Andy' ORDER BY last_name DESC;+----+-------------+--------+------+---------------+------+---------+------+-------+-----------------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+--------+------+---------------+------+---------+------+-------+-----------------------------+| 1 | SIMPLE | foobar | ALL | idx_name | NULL | NULL | NULL | 99391 | Using where; Using filesort |+----+-------------+--------+------+---------------+------+---------+------+-------+-----------------------------+1 row in set (0.00 sec)EXPLAIN SELECT * FROM foobar WHERE first_name = 'Andy' AND last_name > 'Ddufresne' ORDER BY last_name;+----+-------------+--------+-------+---------------+----------+---------+------+------+-----------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+--------+-------+---------------+----------+---------+------+------+-----------------------+| 1 | SIMPLE | foobar | range | idx_name | idx_name | 184 | NULL | 1 | Using index condition |+----+-------------+--------+-------+---------------+----------+---------+------+------+-----------------------+1 row in set (0.00 sec)使用两个不同索引EXPLAIN SELECT * FROM foobar ORDER BY id,first_name LIMIT 10;+----+-------------+--------+------+---------------+------+---------+------+-------+----------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+--------+------+---------------+------+---------+------+-------+----------------+| 1 | SIMPLE | foobar | ALL | NULL | NULL | NULL | NULL | 99391 | Using filesort |+----+-------------+--------+------+---------------+------+---------+------+-------+----------------+1 row in set (0.00 sec)非前缀匹配EXPLAIN SELECT * FROM foobar WHERE last_name='Ddufresne' ORDER BY first_name; +----+-------------+--------+------+---------------+------+---------+------+-------+-----------------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+--------+------+---------------+------+---------+------+-------+-----------------------------+| 1 | SIMPLE | foobar | ALL | NULL | NULL | NULL | NULL | 99391 | Using where; Using filesort |+----+-------------+--------+------+---------------+------+---------+------+-------+-----------------------------+1 row in set (0.00 sec)采用不同排序策略EXPLAIN SELECT * FROM foobar ORDER BY first_name DESC, last_name ASC LIMIT 10;+----+-------------+--------+------+---------------+------+---------+------+-------+----------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+--------+------+---------------+------+---------+------+-------+----------------+| 1 | SIMPLE | foobar | ALL | NULL | NULL | NULL | NULL | 99391 | Using filesort |+----+-------------+--------+------+---------------+------+---------+------+-------+----------------+1 row in set (0.00 sec)数据过滤与排序采用不同索引 ???? 貌似部分情况可以使用EXPLAIN SELECT * FROM foobar WHERE first_name='Andy' ORDER BY id;+----+-------------+--------+------+---------------+----------+---------+-------+-------+----------------------------------------------------+| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |+----+-------------+--------+------+---------------+----------+---------+-------+-------+----------------------------------------------------+| 1 | SIMPLE | foobar | ref | idx_name | idx_name | 92 | const | 19244 | Using index condition; Using where; Using filesort |+----+-------------+--------+------+---------------+----------+---------+-------+-------+----------------------------------------------------+1 row in set (0.00 sec)排序字段不只有字段,可能有函数处理SELECT * FROM t1 ORDER BY ABS(key);SELECT * FROM t1 ORDER BY -key;• The query joins many tables, and the columns in the ORDER BY are not all from the first nonconstant table that is used to retrieve rows. (This is the first table in the EXPLAIN output that does not have a const join type.) • The query has different ORDER BY and GROUP BY expressions. • There is an index on only a prefix of a column named in the ORDER BY clause. In this case, the index cannot be used to fully resolve the sort order. For example, if only the first 10 bytes of a CHAR(20) column are indexed, the index cannot distinguish values past the 10th byte and a filesort will be needed. • The index does not store rows in order. For example, this is true for a HASH index in a MEMORY table.
+
+
+
+
+
+
+
+
+{% highlight text %}
+{% endhighlight %}
diff --git a/_drafts/2015-06-12-mysql-process_init.md b/_drafts/2015-06-12-mysql-process_init.md
new file mode 100644
index 0000000..94ad787
--- /dev/null
+++ b/_drafts/2015-06-12-mysql-process_init.md
@@ -0,0 +1,61 @@
+---
+title: MySQL 执行过程
+layout: post
+comments: true
+language: chinese
+category: [mysql,database]
+keywords: mysql,执行
+description:
+---
+
+
+
+
+
+
+
+
+关于 SQL 的查询,源码可以直接从 mysql_select() 开始,其中包括其优化结果等信息都是保存在 JOIN 结构体中的,它主要包括如下的三个操作:
+
+* JOIN::prepare()
+
+ 用于为整个查询做准备工作,包括分配内存、处理别名、通配符的处理、检查表是否可以访问、检查字段、检查非 group 函数、准备 procedure 等工作。
+
+* JOIN::optimize()
+
+ 整个查询优化器的核心内容,主要对 join 进行简化、优化 where 条件、优化 having 条件、裁剪分区 partition (如果查询的表是分区表)、优化 count()/min()/max() 聚集函数、统计 join 的代价、搜索最优的 join 顺序、生成执行计划、执行基本的查询、优化多个等式谓词、执行 join 查询、优化 distinct、创建临时表存储临时结果等操作。
+
+* JOIN::exec()
+
+ 负责执行优化后的执行计划。
+
+
+## 源码相关
+
+其中与 JOIN 相关的头文件代码在 sql/sql_optimizer.h 中,包括了相关类以及接口的定义。
+
+{% highlight cpp %}
+class JOIN :public Sql_alloc
+{
+ bool need_tmp; // 是否需要tmp文件
+ bool plan_is_const() const; // 是否为常量,例如返回空或者一行记录
+ Next_select_func first_select; // 记录读取首条记录的函数,默认为sub_select()
+ Next_select_func get_end_select_func();
+
+
+ bool is_optimized() const { return optimized; }
+ void set_optimized() { optimized= true; }
+ bool is_executed() const { return executed; }
+ void set_executed() { executed= true; }
+ void reset_executed() { executed= false; }
+private:
+ bool optimized; ///< flag to avoid double optimization in EXPLAIN
+ bool executed; ///< Set by exec(), reset by reset()
+
+
+}
+{% endhighlight %}
+
+
+{% highlight text %}
+{% endhighlight %}
diff --git a/_drafts/2015-06-15-mysql-optimizer-switch_init.md b/_drafts/2015-06-15-mysql-optimizer-switch_init.md
new file mode 100644
index 0000000..acd1029
--- /dev/null
+++ b/_drafts/2015-06-15-mysql-optimizer-switch_init.md
@@ -0,0 +1,284 @@
+---
+Date: October 19, 2013
+title: MySQL 优化开关
+layout: post
+comments: true
+language: chinese
+category: [mysql,database]
+---
+
+
+
+
+![mysql optimizer]({{ site.url }}/images/databases/mysql/optimizer-logo.png "mysql optimizer"){: .pull-center width="80%" }
+
+## 简介
+
+其中,MySQL 存在一系列的优化器开关,可以通过如下命令查看以及设置。
+
+{% highlight text %}
+----- 查看优化器选项
+mysql> SHOW VARIABLES LIKE 'optimizer_switch';
+
+----- 关闭ICP选项,其它优化器选项不变
+mysql> SET optimizer_switch='index_condition_pushdown=off';
+{% endhighlight %}
+
+
+### 源码相关
+
+MySQL 优化器相关的开关,与源码相关的主要涉及 sql/{sql_const.h,sql_class.h,sys_vars.cc} 几个文件,相关内容分别如下。
+
+{% highlight c %}
+/* @@optimizer_switch flags. These must be in sync with optimizer_switch_typelib */
+#define OPTIMIZER_SWITCH_INDEX_MERGE (1ULL << 0)
+#define OPTIMIZER_SWITCH_INDEX_MERGE_UNION (1ULL << 1)
+#define OPTIMIZER_SWITCH_INDEX_MERGE_SORT_UNION (1ULL << 2)
+#define OPTIMIZER_SWITCH_INDEX_MERGE_INTERSECT (1ULL << 3)
+#define OPTIMIZER_SWITCH_ENGINE_CONDITION_PUSHDOWN (1ULL << 4)
+#define OPTIMIZER_SWITCH_INDEX_CONDITION_PUSHDOWN (1ULL << 5)
+#define OPTIMIZER_SWITCH_MRR (1ULL << 6)
+#define OPTIMIZER_SWITCH_MRR_COST_BASED (1ULL << 7)
+#define OPTIMIZER_SWITCH_BNL (1ULL << 8)
+#define OPTIMIZER_SWITCH_BKA (1ULL << 9)
+#define OPTIMIZER_SWITCH_MATERIALIZATION (1ULL << 10)
+#define OPTIMIZER_SWITCH_SEMIJOIN (1ULL << 11)
+#define OPTIMIZER_SWITCH_LOOSE_SCAN (1ULL << 12)
+#define OPTIMIZER_SWITCH_FIRSTMATCH (1ULL << 13)
+#define OPTIMIZER_SWITCH_DUPSWEEDOUT (1ULL << 14)
+#define OPTIMIZER_SWITCH_SUBQ_MAT_COST_BASED (1ULL << 15)
+#define OPTIMIZER_SWITCH_USE_INDEX_EXTENSIONS (1ULL << 16)
+#define OPTIMIZER_SWITCH_COND_FANOUT_FILTER (1ULL << 17)
+#define OPTIMIZER_SWITCH_DERIVED_MERGE (1ULL << 18)
+#define OPTIMIZER_SWITCH_LAST (1ULL << 19)
+{% endhighlight %}
+
+
+{% highlight c %}
+/** Tells whether the given optimizer_switch flag is on */
+inline bool optimizer_switch_flag(ulonglong flag) const
+{
+ return (variables.optimizer_switch & flag);
+}
+{% endhighlight %}
+
+{% highlight c %}
+static const char *optimizer_switch_names[]=
+{
+ "index_merge", "index_merge_union", "index_merge_sort_union",
+ "index_merge_intersection", "engine_condition_pushdown",
+ "index_condition_pushdown" , "mrr", "mrr_cost_based",
+ "block_nested_loop", "batched_key_access",
+ "materialization", "semijoin", "loosescan", "firstmatch", "duplicateweedout",
+ "subquery_materialization_cost_based",
+ "use_index_extensions", "condition_fanout_filter", "derived_merge",
+ "default", NullS
+};
+static Sys_var_flagset Sys_optimizer_switch(
+ "optimizer_switch",
+ "optimizer_switch=option=val[,option=val...], where option is one of "
+ "{index_merge, index_merge_union, index_merge_sort_union, "
+ "index_merge_intersection, engine_condition_pushdown, "
+ "index_condition_pushdown, mrr, mrr_cost_based"
+ ", materialization, semijoin, loosescan, firstmatch, duplicateweedout,"
+ " subquery_materialization_cost_based"
+ ", block_nested_loop, batched_key_access, use_index_extensions,"
+ " condition_fanout_filter, derived_merge} and val is one of "
+ "{on, off, default}",
+ SESSION_VAR(optimizer_switch), CMD_LINE(REQUIRED_ARG),
+ optimizer_switch_names, DEFAULT(OPTIMIZER_SWITCH_DEFAULT),
+ NO_MUTEX_GUARD, NOT_IN_BINLOG, ON_CHECK(NULL), ON_UPDATE(NULL));
+{% endhighlight %}
+
+## 优化详解
+
+数据仍然参考 [MySQL 基本介绍](/post/mysql-basic.html) 中的第一个示例,为了测试方便,需要添加一个索引。
+
+{% highlight text %}
+mysql> CREATE INDEX idx_contacter ON customers (contactLastName, contactFirstName);
+{% endhighlight %}
+
+接下来,就挨个看看各种优化选项。
+
+### Index Condition Pushdown, ICP
+
+ICP 是 MySQL 5.6 新增的特性,是一种在存储引擎层使用索引过滤数据的优化方式。
+
+{% highlight text %}
+----- 查看优化器选项
+mysql> SHOW VARIABLES LIKE 'optimizer_switch';
+
+----- 关闭ICP选项,其它优化器选项不变
+mysql> SET optimizer_switch='index_condition_pushdown=off';
+
+----- ICP示例
+mysql> EXPLAIN SELECT * FROM customers WHERE contactLastName = 'Young' AND contactFirstName LIKE 'J%';
++----+-------------+-----------+------------+-------+---------------+---------------+---------+------+------+----------+-----------------------+
+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
++----+-------------+-----------+------------+-------+---------------+---------------+---------+------+------+----------+-----------------------+
+| 1 | SIMPLE | customers | NULL | range | idx_contacter | idx_contacter | 104 | NULL | 1 | 100.00 | Using index condition |
++----+-------------+-----------+------------+-------+---------------+---------------+---------+------+------+----------+-----------------------+
+1 row in set, 1 warning (0.01 sec)
+{% endhighlight %}
+
+在源码 sql/sql_select.cc 文件中,有如下的相关注释,介绍在什么条件下使用 ICP 。
+
+{% highlight c %}
+/*
+ We will only attempt to push down an index condition when the
+ following criteria are true:
+ 0. The table has a select condition
+ 1. The storage engine supports ICP.
+ 2. The index_condition_pushdown switch is on and
+ the use of ICP is not disabled by the NO_ICP hint.
+ 3. The query is not a multi-table update or delete statement. The reason
+ for this requirement is that the same handler will be used
+ both for doing the select/join and the update. The pushed index
+ condition might then also be applied by the storage engine
+ when doing the update part and result in either not finding
+ the record to update or updating the wrong record.
+ 4. The JOIN_TAB is not part of a subquery that has guarded conditions
+ that can be turned on or off during execution of a 'Full scan on NULL
+ key'.
+ @see Item_in_optimizer::val_int()
+ @see subselect_single_select_engine::exec()
+ @see TABLE_REF::cond_guards
+ @see setup_join_buffering
+ 5. The join type is not CONST or SYSTEM. The reason for excluding
+ these join types, is that these are optimized to only read the
+ record once from the storage engine and later re-use it. In a
+ join where a pushed index condition evaluates fields from
+ tables earlier in the join sequence, the pushed condition would
+ only be evaluated the first time the record value was needed.
+ 6. The index is not a clustered index. The performance improvement
+ of pushing an index condition on a clustered key is much lower
+ than on a non-clustered key. This restriction should be
+ re-evaluated when WL#6061 is implemented.
+ 7. The index on virtual generated columns is not supported for ICP.
+*/
+{% endhighlight %}
+
+
+
+a 当关闭ICP时,index 仅仅是data access 的一种访问方式,存储引擎通过索引回表获取的数据会传递到MySQL Server 层进行where条件过滤。
+b 当打开ICP时,如果部分where条件能使用索引中的字段,MySQL Server 会把这部分下推到引擎层,可以利用index过滤的where条件在存储引擎层进行数据过滤,而非将所有通过index access的结果传递到MySQL server层进行where过滤.
+优化效果:ICP能减少引擎层访问基表的次数和MySQL Server 访问存储引擎的次数,减少io次数,提高查询语句性能。
+
+
+
+{% highlight text %}
+index_read()/general_fetch()
+ |-row_search_mvcc()
+ |-row_search_idx_cond_check()
+ |-innobase_index_cond()
+
+JOIN::optimize()
+ |-make_join_readinfo()
+ |-push_index_cond() 将索引条件下发到存储引擎
+{% endhighlight %}
+
+
+
+
+
+### Multi-Range Read, MRR
+
+MySQL 5.6 版本,对于二级索引的范围扫描并且需要回表的情况,进行的优化;主要是减少随机 IO,并且将随机 IO 转化为顺序 IO,提高查询效率。
+
+MRR 适用于以下两种情况:
+
+1. range access;
+2. ref and eq_ref access, when they are using Batched Key Access 。
+
+另外,使用索引的等值 JOIN 查询也可以进行优化。
+
+#### 原理
+
+详细来说,其原理是,将多个需要回表的二级索引根据主键进行排序,然后一起回表,将原来的回表时进行的随机 IO,转变成顺序 IO。
+
+![optimizer switch mrr]({{ site.url }}/images/databases/mysql/optimizer_switch_mrr_1.png "optimizer switch mrr"){: .pull-center }
+
+如上是没有开启 MRR 的情况下,MySQL 执行查询的伪代码大致如下:
+
+{% highlight text %}
+1. 根据where条件中的二级索引获取二级索引与主键的集合,结果集为result。
+ select key_column, pk_column from table where key_column=XXX order by key_column
+2. 通过第一步获取的主键来获取对应的值。
+ for each pk_column value in result do:
+ select non_key_column from table where pk_column=val
+{% endhighlight %}
+
+由于 MySQL 存储数据的方式导致二级索引的存储顺序与主键的存储顺序不一致,如果根据二级索引获取的主键来访问表中的数据会导致大量的随机 IO,尤其当不同主键不在同一个 page 里面时,必然导致多次 IO 和随机读。
+
+![optimizer switch mrr]({{ site.url }}/images/databases/mysql/optimizer_switch_mrr_2.png "optimizer switch mrr"){: .pull-center }
+
+上图是在使用 MRR 优化特性的情况下,此时 MySQL 对于基于二级索引的查询策略为:
+
+{% highlight text %}
+1. 根据where条件中的二级索引获取二级索引与主键的集合,结果集为result。
+ select key_column, pk_column from table where key_column=XXX order by key_column
+2. 将结果集result放在缓存中(read_rnd_buffer_size大小直到buffer满了),
+ 然后对结果集result按照pk_column排序,得到结果集是result_sort
+3. 利用已经排序过的结果集,访问表中的数据,此时是顺序IO
+ select non_key_column from table where pk_column in (result_sort)
+{% endhighlight %}
+
+MySQL 根据二级索引获取的结果集根据主键排序,将乱序化为有序,在用主键顺序访问基表时,将随机读转化为顺序读,多页数据记录可一次性读入或根据此次的主键范围分次读入,以减少 IO 操作,提高查询效率。
+
+#### 配置使用
+
+关于该优化选项的查看和配置使用方式如下:
+
+{% highlight text %}
+----- 查看mrr/mrr_cost_based是否开启
+mysql> SHOW VARIABLES LIKE 'optimizer_switch';
+
+----- 打开开关
+mysql> SET GLOBAL optimizer_switch ='mrr=on,mrr_cost_based=on';
+{% endhighlight %}
+
+mrr=on 表示启用 MRR 优化;mrr_cost_based 是否通过 cost base 的方式来启用 MRR;如果选择 mrr=on,mrr_cost_based=off 则表示总是开启 MRR 优化; 参数 read_rnd_buffer_size 用来控制键值缓冲区的大小。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## 参考
+
+
+
+
+http://dev.mysql.com/doc/refman/en/mrr-optimization.html
+
+
+
+
+{% highlight text %}
+{% endhighlight %}
diff --git a/_drafts/2015-06-15-mysql-optimizer_init.md b/_drafts/2015-06-15-mysql-optimizer_init.md
new file mode 100644
index 0000000..da5472e
--- /dev/null
+++ b/_drafts/2015-06-15-mysql-optimizer_init.md
@@ -0,0 +1,629 @@
+---
+Date: October 19, 2013
+title: MySQL 优化详解
+layout: post
+comments: true
+language: chinese
+category: [mysql,database]
+---
+
+优化应该是 MySQL 中比较复杂的一部分了。
+
+
+
+![mysql optimizer]({{ site.url }}/images/databases/mysql/optimizer-logo.png "mysql optimizer"){: .pull-center width="80%" }
+
+
+## 执行计划
+
+可以通过 explain 查看 MySQL 的执行计划。
+
+{% highlight text %}
+mysql> [explain|describe|desc] [extended|partitions] select_clause;
+{% endhighlight %}
+
+使用 extended 参数后,会包含了优化器优化后的 select 查询语句,可以通过 show warnings 查看;partitions 是用于分区表的查询语句;示例如下:
+
+{% highlight text %}
+mysql> explain select emp_no from salaries where salary = 90930;
++----+-------------+----------+------------+------+---------------+------+---------+------+---------+----------+-------------+
+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
++----+-------------+----------+------------+------+---------------+------+---------+------+---------+----------+-------------+
+| 1 | SIMPLE | salaries | NULL | ALL | NULL | NULL | NULL | NULL | 2838718 | 10.00 | Using where |
++----+-------------+----------+------------+------+---------------+------+---------+------+---------+----------+-------------+
+1 row in set, 1 warning (0.00 sec)
+{% endhighlight %}
+
+执行计划包含的信息如下:
+
+* **id** 识别符,也就是查询序列号;表示查询中执行 select 子句或操作表的顺序,id 相同,执行顺序由上至下,如果是子查询,id 的序号会递增,id 值越大优先级越高,越先被执行。
+* **select_type** 类型,可以为以下任何一种:
+ 1. SIMPLE: 简单 SELECT,没有使用 UNION 或子查询。
+* **table** 输出的行所引用的表。
+* **type** 联接类型。下面给出各种联接类型,按照从最佳类型到最坏类型进行排序:
+ 1. ALL: Full Table Scan,遍历全表以找到匹配的行。
+ 2. index: Full Index Scan,与 ALL 的区别在于只会遍历索引树,通常比 ALL 快,因为索引文件通常比数据文件小。
+ 3. range: 索引的范围扫描,对索引的扫描开始于某一点,返回匹配值域的行,常见于 in、<、>、between 等查询。
+ 4. ref: 非唯一性索引扫描,返回匹配某个值的所有行;常见于使用非唯一索引或者唯一索引的非唯一前缀进行的查找。
+ 5. eq_ref: 唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配,常见于主键或唯一索引扫描。
+ 6. const, system: 优化后可以转换为一个常量时,如主键查询;其中,system 是 const 类型的特例,当查询的表只有一行的情况下,即为 system 。
+* **possible_keys** 能使用哪个索引在该表中找到行,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用。
+* **key** 实际决定使用的索引,如果没有选择索引,则是 NULL 。
+* **key_len** 使用的键长度,为最大可能长度;如果键是 NULL,则长度为 NULL 。
+* **ref** 显示使用哪个列或常数与 key 一起从表中选择行。
+* **rows** 认为它执行查询时必须检查的行数,多行之间的数据相乘可以估算要处理的行数。
+* **filtered** 显示了通过条件过滤出的行数的百分比估计值。
+* **Extra** 该列包含 MySQL 解决查询的详细信息。
+
+数据库可以参考 [MySQL 基本介绍](/post/mysql-basic.html) 中的第一个示例数据库,另外,需要添加一个索引。
+
+{% highlight text %}
+mysql> CREATE INDEX idx_contacter ON customers (contactLastName, contactFirstName);
+{% endhighlight %}
+
+接下来看看一些常见的示例。
+
+{% highlight text %}
+----- select_type(SIMPLE)
+mysql> EXPLAIN SELECT * FROM employees WHERE employeeNumber = 1002;
++----+-------------+-----------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
+| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
++----+-------------+-----------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
+| 1 | SIMPLE | employees | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
++----+-------------+-----------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
+1 row in set, 1 warning (0.00 sec)
+{% endhighlight %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+###### extra的说明
+Distinct
+MySQL发现第1个匹配行后,停止为当前的行组合搜索更多的行。对于此项没有找到合适的例子,求指点。
+
+Not exists
+因为b表中的order_id是主键,不可能为NULL,所以mysql在用a表的order_id扫描t_order表,并查找b表的行时,如果在b表发现一个匹配的行就不再继续扫描b了,因为b表中的order_id字段不可能为NULL。这样避免了对b表的多次扫描。
+explain select count(1) from t_order a left join t_order_ext b on a.order_id=b.order_id where b.order_id is null;
+
+Range checked for each record
+这种情况是mysql没有发现好的索引可用,速度比没有索引要快得多。
+explain select * from t_order t, t_order_ext s where s.order_id>=t.order_id and s.order_id<=t.order_id and t.express_type>5;
+
+-->
+
+详细内容可以参考 [MySQL Explain](/reference/databases/mysql/mysqlexplain.pdf) 中的介绍。
+
+
+
+
+
+
+
+
+
+
+### 源码解析
+
+EXPLAIN 相关的源码在 sql/opt_explain.cc 文件中,主要处理流程如下。
+
+{% highlight text %}
+mysql_parse()
+ |-mysql_execute_command()
+ |-execute_sqlcom_select()
+ |-handle_query()
+ |-explain_query()
+{% endhighlight %}
+
+
+
+## 配置参数
+
+优化器的搜索空间越小,实际上对应的优化时间越小;同时可能会由于忽略了一些方案导致失去了最佳方案。对于搜索空间,可以通过如下的两个系统变量来控制:
+
+* optimizer_prune_level [1]
+
+ 用来告诉优化器,可以通过评估每个表被访问记录的数量来忽略某些方案. 经验显示,这种类型的"学习猜想(educated guess)",很少会错失掉最佳方案,并会很明显地减少查询编译次数.这也是为什么MYSQL默认会设置:optimizer_prune_level=1的原因. 当然我们也可以将optimizer_prune_level=0,但需要承担编译查询可能会持续很长时间的危险.
+
+* optimizer_search_depth [62]
+
+ 用来告诉优化器,对一个不完整方案的最大评估深度是多少.其值越小,那么查询编译的次数(时间)就会越少.如果此值为0,那么优化器会自动决定其值.
+
+
+如果 search_depth = 0 ,那么实际上是由 MySQL 决定搜索深度的,通过 determine_search_depth() 实现,实际上就是 min(number of tables, 7)。另外,需要主要注意的是,表的数量是不包含常量表的。
+
+实际上,在 MySQL 中,当超过了 7 个表的 JOIN 之后,优化器所带来的成本就非常高了,如 (7!=5040, 8!=40320, 9!=362 880)
+
+
+
+
+
+
+
+
+
+
+
+
+
Index Merge Optimization 索引合并优化
+5.0 之前,每条单表查询只能使用一个索引,之后推出的索引合并优化,可以让 MySQL 在查询中对一个表使用多个索引,对它们同时扫描,并且合并结果。需要注意的是,该优化只能用于单表。
+
+对于该类型的优化,可以通过 explain 查看 type 字段为 index_merge,在 Extra 中显示具体的类型,包括了三种:unions、intersections、unions-of-intersections;详细可以参考 Index Merge Optimization 。
+
+mysql> create index idx_firstname on employees(first_name);
+mysql> create index idx_lastname on employees(last_name);
+
+
+The Index Merge Intersection Access Algorithm
+不同的索引的 range 查询,并通过 AND 链接。
+
+mysql> explain select * from employees where first_name = 'Georgi' and last_name = 'Facello';
+
+
+The Index Merge Union Access Algorithm
+不同的索引的 range 查询,并通过 OR 链接。
+
+mysql> explain select * from employees where first_name = 'Georgi' or last_name = 'Facello';
+
+
+
+The Index Merge Sort-Union Access Algorithm
+
+
+
+
+get_key_scans_params() -> check_quick_select() -> sel_arg_range_seq_next() -> is_key_scan_ror() 用于判断是否为 Rowid-Ordered Retrieval, ROR
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Profile
+
+这一参数可以在全局和 session 级别来设置,该参数开启后,后续执行的 SQL 语句都将记录其资源开销,常见的有 IO、上下文切换、CPU、Memory 等,可以通过 help profile 查看帮助。
+
+{% highlight text %}
+----- 查看profile是否开启
+mysql> show variables like '%profiling%';
++------------------------+-------+
+| Variable_name | Value |
++------------------------+-------+
+| have_profiling | YES | # 只读变量,是否支持profiling
+| profiling | OFF | # 是否开启了语句刨析功能
+| profiling_history_size | 15 | # 保留剖析结果的数目,默认为15[0~100],为0时禁用
++------------------------+-------+
+3 rows in set (0.00 sec)
+
+----- 打开会话级的剖析功能
+mysql> set profiling = on;
+Query OK, 0 rows affected (0.00 sec)
+
+----- 关闭会话级的剖析功能
+mysql> set profiling = off;
+Query OK, 0 rows affected (0.00 sec)
+
+----- 查看当前会话保存的profiling记录
+mysql> show profiles;
++----------+-------------+--------------------------------------------------+
+| Query_ID | Duration | Query |
++----------+-------------+--------------------------------------------------+
+| 1 | 11.24294018 | select emp_no from salaries where salary = 90930 |
++----------+-------------+--------------------------------------------------+
+1 rows in set (0.00 sec)
+
+----- 列出具体消耗时间的详细信息,1 表示query_id
+mysql> show profile for query 1;
++----------------------+-----------+
+| Status | Duration |
++----------------------+-----------+
+| starting | 0.000095 |
+| checking permissions | 0.000008 |
+| Opening tables | 0.000034 |
+| After opening tables | 0.000007 |
+| System lock | 0.000006 |
+| Table lock | 0.000011 |
+| init | 0.000020 |
+| optimizing | 0.000017 |
+| statistics | 0.000023 |
+| preparing | 0.000025 |
+| executing | 0.000003 |
+| Sending data | 11.242564 |
+| end | 0.000014 |
+| query end | 0.000012 |
+| closing tables | 0.000005 |
+| Unlocking tables | 0.000017 |
+| freeing items | 0.000013 |
+| updating status | 0.000059 |
+| cleaning up | 0.000009 |
++----------------------+-----------+
+19 rows in set (0.00 sec)
+
+----- 其它的还支持查看cpu、memory、block io等方式查看,如下为查看CPU
+mysql> show profile cpu for query 1;
+{% endhighlight %}
+
+
+
+
+
+
+## Optimizer Trace
+
+在 MySQL5.6 中,支持将执行的 SQL 的查询计划树记录下来,该功能可以通过一个会话级的参数 optimizer_trace 进行控制,默认是关闭的,因为这会影响查询的性能。
+
+{% highlight text %}
+----- 1. 查看相关变量是否打开,如果没有则打开
+mysql> SHOW VARIABLES LIKE 'optimizer_trace%';
+mysql> SET optimizer_trace="enabled=on";
+
+----- 2. 执行一些查询,注意:只保留最后一次的结果
+... ...
+
+mysql> SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE; # 查询结果,或者如下导入到文件中
+mysql> SELECT TRACE INTO DUMPFILE "xx.trace" FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;
+{% endhighlight %}
+
+注意,上述的 dumpfile 会将该文件 dump 到该库所在的目录下。
+
+常见的相关参数如下。
+
+* optimizer_trace,开关控制。
+
+ 有两个字段,"enabled=on,one_line=off",前者表示是否打开 optimizer_trace,后者表示打印的查询计划是以一行显示,还是以 json 树的形式显示,默认为 json 数显示。
+
+* optimizer_trace_limit、optimizer_trace_offset,保存以及显示的记录数。
+
+ 前者是正整数,后者为整数,默认值为 1/-1,也就是只会保存并显示一条记录;注意,重设变量会导致 trace 被清空。
+
+* optimizer_trace_max_mem_size,内存使用大小。
+
+ trace 数据会存储在内存中,通过该参数设置最大的内存使用数;该参数是 session 级别,不应该设置的过大,而且不应该超过 OPTIMIZER_TRACE_MAX_MEM_SIZE,默认是 16K。
+
+* optimizer_trace_features,控制打印查询计划树的选项。
+
+ 当不关心某些查询计划选项时,可以将其关闭掉,只打印关注的,这样可以减小查询计划树的输出。
+
+
+### 源码解析
+
+在源码中,通过 trace_wrapper()、trace_prepare()、trace_steps() 等函数实现。如下是一个很简单的查询的执行计划 select * from customers limit 100, 2; 。
+
+
+{% highlight text %}
+{
+ "steps": [
+ {"join_preparation": { 在JOIN::prepare()中
+ "select#": 1,
+ "steps": [
+ {
+ "expanded_query": "/* select#1 */..."
+ }
+ ]
+ }
+ },
+
+ {"join_optimization": { 在JOIN::optimize()中
+ "select#": 1,
+ "steps": [
+ {
+ "table_dependencies": [
+ {
+ "table": "`customers`",
+ "row_may_be_null": false,
+ "map_bit": 0,
+ "depends_on_map_bits": [
+ ]
+ }
+ ]
+ },
+ {
+ "rows_estimation": [
+ {
+ "table": "`customers`",
+ "table_scan": {
+ "rows": 122,
+ "cost": 3
+ }
+ }
+ ]
+ },
+ {
+ "considered_execution_plans": [
+ {
+ "plan_prefix": [
+ ],
+ "table": "`customers`",
+ "best_access_path": {
+ "considered_access_paths": [
+ {
+ "access_type": "scan",
+ "rows": 122,
+ "cost": 27.4,
+ "chosen": true
+ }
+ ]
+ },
+ "cost_for_plan": 27.4,
+ "rows_for_plan": 122,
+ "chosen": true
+ }
+ ]
+ },
+ {
+ "attaching_conditions_to_tables": {
+ "original_condition": null,
+ "attached_conditions_computation": [
+ ],
+ "attached_conditions_summary": [
+ {
+ "table": "`customers`",
+ "attached": null
+ }
+ ]
+ }
+ },
+ {
+ "refine_plan": [
+ {
+ "table": "`customers`",
+ "access_type": "table_scan"
+ }
+ ]
+ }
+ ]
+ }
+ },
+
+ {
+ "join_execution": {
+ "select#": 1,
+ "steps": [
+ ]
+ }
+ }
+ ]
+}
+{% endhighlight %}
+
+可以查看官方的介绍 [Tracing the Optimizer](http://dev.mysql.com/doc/internals/en/optimizer-tracing.html),以及其中的示例。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## 参考
+
+关于 MySQL 的大部分耗时都消耗在那里了,以及作者是如何通过 HandlerSocket 插件大幅度提高访问性能的,可以参考 [Using MySQL as a NoSQL - A story for exceeding 750,000 qps on a commodity server](http://yoshinorimatsunobu.blogspot.com/2010/10/using-mysql-as-nosql-story-for.html),或者直接参考 [本地版本](/reference/mysql/Using MySQL as a NoSQL.mhtml) 。
+
+
+
+
+{% highlight text %}
+{% endhighlight %}
diff --git a/_drafts/2015-06-18-mysql-innodb-buffer-pool_init.md b/_drafts/2015-06-18-mysql-innodb-buffer-pool_init.md
new file mode 100644
index 0000000..73cb4a7
--- /dev/null
+++ b/_drafts/2015-06-18-mysql-innodb-buffer-pool_init.md
@@ -0,0 +1,1034 @@
+---
+title: InnoDB Buffer Pool
+layout: post
+comments: true
+language: chinese
+category: [mysql,database]
+keywords: mysql,innodb,buffer pool
+description: 无论哪种数据库,缓存都是提高数据库性能的关键技术,通过缓存可以在读写方面大幅提高数据库的整体性能,InnoDB 中就是通过 Buffer Pool 实现的。在此,简单介绍下 Buffer Pool 中的实现。
+---
+
+无论哪种数据库,缓存都是提高数据库性能的关键技术,通过缓存可以在读写方面大幅提高数据库的整体性能,InnoDB 中就是通过 Buffer Pool 实现的。
+
+在此,简单介绍下 Buffer Pool 中的实现。
+
+
+
+## 简介
+
+我们知道 InnoDB 使用 buffer pool 来缓存从磁盘读取到内存的数据页,BP 通常由数个内存块加上一组控制结构体对象组成,BP 每块内存通过 mmap 分配内存,而这些大片的内存块又按照 16KB 划分为多个 frame,用于存储数据页。
+
+多数情况下 BP 是以 16KB 来存储数据页,但有一种例外:使用压缩表时,需要在内存中同时存储压缩页和解压页,对于压缩页,会使用 Binary buddy allocator 算法来分配内存空间。
+
+例如我们读入一个 8KB 的压缩页,就从 BP 中取一个 16KB 的 block,取其中 8KB,剩下的 8KB 放到空闲链表上;如果紧跟着另外一个 4KB 的压缩页读入内存,就可以从这 8KB 中分裂 4KB,同时将剩下的 4KB 放到空闲链表上。
+
+BP 通过 Least Recently Used, LRU 进行组织,当需要插入新数据时,将最近最少使用的 block 去除,并将新 block 插入到列表的中间,也就是 ```midpoint insertion strategy``` 。
+
+头部的子链表,表示最近读取过;尾部的子链表表示最近很少读取;也就是说经常使用的 blocks 会放在头部,而需要去除的会放在尾部。
+
+### 算法详解
+
+单纯的 LRU 算法有一个缺点:如果有某一个查询做了一次全表扫描,如备份、DDL 等,都可能会导致整个 LRU 链表中的数据块都被替换了,甚至很多热点数据也会被替换,而这些新进的数据块可能在这一次查询之后就再也不会被读到了;此时也就是说 BP 被污染了。
+
+即使采用上述的 midpoint 方法,也就是说当数据块需要从数据文件中读取时 (也包括了预读),首先会放到 old sublist 的头部
+(midpoint)。然后,如果有对这个数据块的访问,那么就将这个数据块放到 new sublist 的首部。
+
+一般来说,一个数据块被取出后,立刻会有读取,也就很快会被放到 new sublist 的头部。一种糟糕的情况是,如果是 mysqldump 访问全部数据块,也就会导致所有的数据块被放到 new sublist;这样 BP 也会被全部污染。
+
+为了解决这个问题,可以设置 ```innodb_old_blocks_time(ms)``` 参数,当页被插入到 midpoint 后,必须要在 old sublist 的头部停留超过上述的时间后,才有可能被转移到 new sublist。
+
+参数 ```innodb_old_blocks_time``` 可以动态设置,在执行一些全表扫描时,可以将上述参数设置为比较大的值,操作完成之后再恢复为 0 。
+
+{% highlight text %}
+SET GLOBAL innodb_old_blocks_time = 1000;
+... perform queries that scan tables ...
+SET GLOBAL innodb_old_blocks_time = 0;
+{% endhighlight %}
+
+还有一种情况时,希望数据加载的缓存中。例如,在进行性能测试时,通常会执行一次全表扫描,将磁盘中的数据加载到内存中,那么,此时就应该将上述的参数设置为 0 。
+
+### 多实例配置
+
+
+
+当服务器的内存比较大时,如果多个线程读取 BP 数据,会导致瓶颈;此时,可以将 BP 分为几个实例 (instance),从而提高并发,减少资源冲突。每个实例管理自己的 free lists、flush lists、LRUs、互斥锁 (mutex) 以及其它与 BP 相关的结构。
+
+可以通过 innodb_buffer_pool_instances 参数配置实例个数,默认为 1,最大可以配置为 64;只有当 innodb_buffer_pool_size 的值大于 1G 时才会生效,而且每个实例均分 BP 缓存。
+
+通过多个 BP 可以减小竞争,每个页(page)将通过一个 hash 函数随机分配到 BP 中。
+
+### 参数、监控
+
+InnoDB 主索引是聚簇索引,索引与数据共用表空间,也就是说,对于 InnoDB 而言,数据就是索引,索引就是数据。也就是说,Innodb 和 MyISAM 缓存的最大区别就在于前者不仅缓存索引,同时还会缓存数据。
+
+#### 参数设置
+
+通过 innodb_buffer_pool_size 参数来设置 InnoDB 缓存池大小,也就是缓存用户表及索引数据的最主要缓存空间,甚至其它管理数据 (例如元数据信息,行级锁信息);对于专用的数据库服务器上通常为物理内存的 70% ~ 80%;5.7.5 版本后可以动态调整,调整时按 Block 进行。
+
+因此,对 Innodb 整体性能影响也最大。可通过如下命令查看当前 buffer pool 相关的参数。
+
+{% highlight text %}
+----- 当前缓存的大小
+mysql> SHOW VARIABLES LIKE 'innodb%pool%';
++-----------------------------------------+----------------+
+| Variable_name | Value |
++-----------------------------------------+----------------+
+| innodb_buffer_pool_chunk_size | 134217728 | 动态调整时Block的大小
+| innodb_buffer_pool_dump_at_shutdown | ON |
+| innodb_buffer_pool_dump_now | OFF |
+| innodb_buffer_pool_dump_pct | 25 |
+| innodb_buffer_pool_evict | |
+| innodb_buffer_pool_filename | ib_buffer_pool |
+| innodb_buffer_pool_instances | 1 | BP缓存的实例数,只有当BP大于1G时才会生效
+| innodb_buffer_pool_load_abort | OFF |
+| innodb_buffer_pool_load_at_startup | ON |
+| innodb_buffer_pool_load_now | OFF |
+| innodb_buffer_pool_size | 134217728 | 缓存大小,包括了数据和索引
+| innodb_disable_resize_buffer_pool_debug | ON |
++-----------------------------------------+----------------+
+12 rows in set (0.00 sec)
+
+----- 当前缓存页的大小
+mysql> SHOW VARIABLES LIKE 'innodb_page_size';
++------------------+-------+
+| Variable_name | Value |
++------------------+-------+
+| innodb_page_size | 16384 |
++------------------+-------+
+1 row in set (0.00 sec)
+{% endhighlight %}
+
+如上所述,通常建议设置为系统内存的 70%~80%,不过也必须要对具体项目具体分析,比如最大链接数、是否有 MyISAM 引擎等。可以按照如下的值进行分配,仅做参考:
+
+{% highlight text %}
+操作系统:
+ 800M~1G
+线程独享:
+ 2GB = 线程数(500) * (1MB + 1MB + 1MB + 512KB + 512KB)
+ sort_buffer_size(1MB) + join_buffer_size(1MB) + read_buffer_size (1MB)
+ read_rnd_buffer_size(512KB) + thread_statck(512KB)
+MyISAM Key Cache:
+ 1.5GB
+Innodb Buffer Pool:
+ 8GB(系统内存) - 800MB - 2GB - 1.5GB = 3.7GB
+{% endhighlight %}
+
+修改配置文件 /etc/my.cnf,并添加如下字段,然后重启 mysqld 即可。
+
+{% highlight text %}
+[mysqld]
+innodb_buffer_pool_size = 3G
+{% endhighlight %}
+
+除了上述的参数之外,与之相关的还有如下配置参数:
+
+* innodb_old_blocks_pct old-sublist 的分割点,可以为 5~95,默认为 37 也就是 3/8;
+* innodb_old_blocks_time (ms) 指定页必须在 old-sublist 中待多长时间之后,才有可能被移动到新列表中;
+
+
+
+
+
+
+
+
+#### 监控指标
+
+可以通过如下命令查看一些 InnoDB Buffer Pool 监控值。
+
+{% highlight text %}
+mysql> SHOW STATUS LIKE 'Innodb_buffer_pool_%';
++-----------------------------------+-------+
+| Variable_name | Value |
++-----------------------------------+-------+
+| Innodb_buffer_pool_pages_data | 70 | 已经使用的缓存页数
+| Innodb_buffer_pool_pages_dirty | 0 | 脏页数
+| Innodb_buffer_pool_pages_flushed | 0 | 刷新页数
+| Innodb_buffer_pool_pages_free | 1978 | 尚未使用的缓存页数
+| Innodb_buffer_pool_pages_misc | 0 |
+| Innodb_buffer_pool_pages_total | 2048 | 缓存页面总数,innodb_page_size(16k)
+| Innodb_buffer_pool_read_ahead_rnd | 1 |
+| Innodb_buffer_pool_read_ahead_seq | 0 |
+| Innodb_buffer_pool_read_requests | 329 | 读请求次数
+| Innodb_buffer_pool_reads | 19 | 从磁盘中读取数据的次数
+| Innodb_buffer_pool_wait_free | 0 |
+| Innodb_buffer_pool_write_requests | 0 |
++-----------------------------------+-------+
+12 rows in set (0.01 sec)
+{% endhighlight %}
+
+如果 Innodb_buffer_pool_pages_free 偏大的话,证明有很多缓存没有被利用到,这时可以考虑减小缓存;相反 Innodb_buffer_pool_pages_data 过大就考虑增大缓存。
+
+对于缓存命中率可以通过如下方式计算。
+
+{% highlight text %}
+----- 缓存命中率,通常不低于99%
+(Innodb_buffer_pool_read_requests - Innodb_buffer_pool_reads) / Innodb_buffer_pool_read_requests * 100%
+(329 - 19) / 329 * 100% = 94.22%。
+{% endhighlight %}
+
+
+
+另外,也可以通过 ```SHOW ENGINE INNODB STATUS\G``` 命令查看。
+
+{% highlight text %}
+mysql> SHOW ENGINE INNODB STATUS\G
+----------------------
+BUFFER POOL AND MEMORY
+----------------------
+Total large memory allocated 140836864
+Dictionary memory allocated 538303
+Buffer pool size 8192
+Free buffers 7810
+Database pages 382
+Old database pages 0
+Modified db pages 0
+Pending reads 0
+Pending writes: LRU 0, flush list 0, single page 0
+Pages made young 0, not young 0
+0.00 youngs/s, 0.00 non-youngs/s
+Pages read 336, created 46, written 128
+0.00 reads/s, 0.00 creates/s, 0.00 writes/s
+No buffer pool page gets since the last printout
+Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
+LRU len: 382, unzip_LRU len: 0
+I/O sum[0]:cur[0], unzip sum[0]:cur[0]
+{% endhighlight %}
+
+可以查看对应的源码。
+
+{% highlight c %}
+ibool srv_printf_innodb_monitor(FILE* file, ibool nowait,
+ ulint* trx_start_pos, ulint* trx_end)
+{
+ // ... ...
+ fputs("----------------------\n"
+ "BUFFER POOL AND MEMORY\n"
+ "----------------------\n", file);
+ fprintf(file,
+ "Total large memory allocated " ULINTPF "\n"
+ "Dictionary memory allocated " ULINTPF "\n",
+ os_total_large_mem_allocated,
+ dict_sys->size);
+
+ buf_print_io(file);
+ // ... ...
+}
+
+void buf_print_io( FILE* file)
+{
+ ulint i;
+ buf_pool_info_t* pool_info;
+ buf_pool_info_t* pool_info_total;
+
+ /* If srv_buf_pool_instances is greater than 1, allocate
+ one extra buf_pool_info_t, the last one stores
+ aggregated/total values from all pools */
+ if (srv_buf_pool_instances > 1) {
+ pool_info = (buf_pool_info_t*) ut_zalloc_nokey((
+ srv_buf_pool_instances + 1) * sizeof *pool_info);
+
+ pool_info_total = &pool_info[srv_buf_pool_instances];
+ } else {
+ ut_a(srv_buf_pool_instances == 1);
+
+ pool_info_total = pool_info =
+ static_cast(
+ ut_zalloc_nokey(sizeof *pool_info));
+ }
+
+ for (i = 0; i < srv_buf_pool_instances; i++) {
+ buf_pool_t* buf_pool;
+
+ buf_pool = buf_pool_from_array(i);
+
+ /* Fetch individual buffer pool info and calculate
+ aggregated stats along the way */
+ buf_stats_get_pool_info(buf_pool, i, pool_info);
+
+ /* If we have more than one buffer pool, store
+ the aggregated stats */
+ if (srv_buf_pool_instances > 1) {
+ buf_stats_aggregate_pool_info(pool_info_total,
+ &pool_info[i]);
+ }
+ }
+
+ /* Print the aggreate buffer pool info */
+ buf_print_io_instance(pool_info_total, file);
+
+ /* If there are more than one buffer pool, print each individual pool
+ info */
+ if (srv_buf_pool_instances > 1) {
+ fputs("----------------------\n"
+ "INDIVIDUAL BUFFER POOL INFO\n"
+ "----------------------\n", file);
+
+ for (i = 0; i < srv_buf_pool_instances; i++) {
+ fprintf(file, "---BUFFER POOL " ULINTPF "\n", i);
+ buf_print_io_instance(&pool_info[i], file);
+ }
+ }
+
+ ut_free(pool_info);
+}
+{% endhighlight %}
+
+上述方式,只能看到所有 BP 的总体统计值,如果要查看每个实例的值,可以通过如下命令查看。
+
+{% highlight text %}
+mysql> SELECT * FROM information_schema.innodb_buffer_pool_stats\G
+{% endhighlight %}
+
+
+## 磁盘预热
+
+如果一台高负荷的机器重启后,内存中大量的热数据被清空;重启后,会重新从磁盘加载到 Buffer Pool 中,此时,性能就会变得很差,连接数就会很高。
+
+在 MySQL5.6 后,添加了一个新特性避免的这种问题的出现,当正常关闭时会将 BP 中的数据写入到磁盘;对于 kill -9 或者宕机仍无法使用。
+
+{% highlight text %}
+----- 关闭时把热数据dump到本地磁盘。
+innodb_buffer_pool_dump_at_shutdown = 1
+
+innodb_buffer_pool_dump_now = 1
+
+解释:采用手工方式把热数据dump到本地磁盘。
+
+innodb_buffer_pool_load_at_startup = 1
+
+解释:在启动时把热数据加载到内存。
+
+innodb_buffer_pool_load_now = 1
+
+解释:采用手工方式把热数据加载到内存。
+
+{% endhighlight %}
+
+在关闭 MySQL 时,会把内存中的热数据保存在磁盘里 ib_buffer_pool 文件中,位于数据目录下。
+
+
+
+
+## 源码解析
+
+MySQL 5.6 内存管理在 mem0pool.c 中实现,其文件的开头注释中都有说明,粗略的可以分成四部分,包含 9 大块。
+
+{% highlight text %}
+/* We would like to use also the buffer frames to allocate memory. This
+would be desirable, because then the memory consumption of the database
+would be fixed, and we might even lock the buffer pool to the main memory.
+The problem here is that the buffer management routines can themselves call
+memory allocation, while the buffer pool mutex is reserved.
+
+The main components of the memory consumption are:
+
+1. buffer pool,
+2. parsed and optimized SQL statements,
+3. data dictionary cache,
+4. log buffer,
+5. locks for each transaction,
+6. hash table for the adaptive index,
+7. state and buffers for each SQL query currently being executed,
+8. session for each user, and
+9. stack for each OS thread.
+
+Items 1 and 2 are managed by an LRU algorithm. Items 5 and 6 can potentially
+consume very much memory. Items 7 and 8 should consume quite little memory,
+and the OS should take care of item 9, which too should consume little memory.
+
+A solution to the memory management:
+
+1. the buffer pool size is set separately;
+2. log buffer size is set separately;
+3. the common pool size for all the other entries, except 8, is set separately.
+
+Problems: we may waste memory if the common pool is set too big. Another
+problem is the locks, which may take very much space in big transactions.
+Then the shared pool size should be set very big. We can allow locks to take
+space from the buffer pool, but the SQL optimizer is then unaware of the
+usable size of the buffer pool. We could also combine the objects in the
+common pool and the buffers in the buffer pool into a single LRU list and
+manage it uniformly, but this approach does not take into account the parsing
+and other costs unique to SQL statements.
+
+The locks for a transaction can be seen as a part of the state of the
+transaction. Hence, they should be stored in the common pool. We still
+have the problem of a very big update transaction, for example, which
+will set very many x-locks on rows, and the locks will consume a lot
+of memory, say, half of the buffer pool size.
+
+Another problem is what to do if we are not able to malloc a requested
+block of memory from the common pool. Then we can request memory from
+the operating system. If it does not help, a system error results.
+
+Because 5 and 6 may potentially consume very much memory, we let them grow
+into the buffer pool. We may let the locks of a transaction take frames
+from the buffer pool, when the corresponding memory heap block has grown to
+the size of a buffer frame. Similarly for the hash node cells of the locks,
+and for the adaptive index. Thus, for each individual transaction, its locks
+can occupy at most about the size of the buffer frame of memory in the common
+pool, and after that its locks will grow into the buffer pool. */
+{% endhighlight %}
+
+### BP 管理概述
+
+为了管理 buffer pool,每个 buffer pool instance 使用如下几个链表来管理:
+
+* LRU 链表包含所有读入内存的数据页;
+* Flush_list 包含被修改过的脏页;
+* unzip_LRU 包含所有解压页;
+* Free list 上存放当前空闲的 block 。
+
+另外为了避免查询数据页时扫描 LRU,还为每个 buffer pool instance 维护了一个 page hash,可以通过 space id 和 page no 直接找到对应的 page 。
+
+一般情况下,当需要读入一个 Page 时,首先会根据 space id 和 page no 找到对应的 buffer pool instance;然后查询 page hash,如果 page hash 中没有,则表示需要从磁盘读取。
+
+在读盘前,我们需要为即将读入内存的数据页分配一个空闲的 block,当 free list 上存在空闲的 block 时,可以直接从 free list 上摘取;如果没有,就需要从 unzip_lru 或者 lru 上淘汰 page 。
+
+在淘汰页时会遵循一定原则,可参考 ```buf_LRU_get_free_block()->buf_LRU_scan_and_free_block()```:
+
+1. 首先尝试从 unzip_lru 上驱逐解压页,可以参考 ```buf_LRU_free_from_unzip_LRU_list()```;
+2. 如果没有,再尝试从 lru 链表上淘汰 Page,可以参考 ```buf_LRU_free_from_common_LRU_list()``` ;
+3. 如果还是无法从 lru 上获取到空闲 block,用户线程就会参与刷脏,尝试做一次 SINGLE PAGE FLUSH,单独从 lru 上刷掉一个脏页,然后再重试。
+
+BP 中的页被修改后,不是立刻写入磁盘,而是由后台线程定时写入,和大多数数据库系统一样,脏页的写盘遵循日志先行 WAL 原则,因此在每个 block 上都记录了一个最近被修改时的 lsn,写数据页时需要确保当前写入日志文件的 redo 不低于这个 lsn。
+
+然而基于 WAL 原则的刷脏策略可能带来一个问题:当数据库的写入负载过高时,产生 redo log 的速度极快,redo log 可能很快到达同步 checkpoint 点,这时候需要进行刷脏来推进 lsn 。
+
+由于这种行为是由用户线程在检查到 redo log 空间不够时触发,大量用户线程将可能陷入到这段低效的逻辑中,产生一个明显的性能拐点。
+
+### 数据结构
+
+
+{% highlight cpp %}
+
+class buf_page_t {
+ unsigned old:1; // 是否在LRU_list的old部分
+};
+
+struct buf_block_t {
+};
+
+struct buf_buddy_free_t {
+};
+
+
+struct buf_pool_t{
+ /** @name General fields */
+ /* @{ */
+ ib_mutex_t mutex; /*!< Buffer pool mutex of this
+ instance */
+ ib_mutex_t zip_mutex; /*!< Zip mutex of this buffer
+ pool instance, protects compressed
+ only pages (of type buf_page_t, not
+ buf_block_t */
+ ulint instance_no; /*!< Array index of this buffer
+ pool instance */
+ ulint old_pool_size; /*!< Old pool size in bytes */
+ ulint curr_pool_size; /*!< Current pool size in bytes */
+ ulint LRU_old_ratio; /*!< Reserve this much of the buffer
+ pool for "old" blocks */
+#ifdef UNIV_DEBUG
+ ulint buddy_n_frames; /*!< Number of frames allocated from
+ the buffer pool to the buddy system */
+#endif
+#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG
+ ulint mutex_exit_forbidden; /*!< Forbid release mutex */
+#endif
+ ulint n_chunks; /*!< number of buffer pool chunks */
+ buf_chunk_t* chunks; /*!< buffer pool chunks */
+ ulint curr_size; /*!< current pool size in pages */
+ hash_table_t* page_hash; /*!< hash table of buf_page_t or
+ buf_block_t file pages,
+ buf_page_in_file() == TRUE,
+ indexed by (space_id, offset).
+ page_hash is protected by an
+ array of mutexes.
+ Changes in page_hash are protected
+ by buf_pool->mutex and the relevant
+ page_hash mutex. Lookups can happen
+ while holding the buf_pool->mutex or
+ the relevant page_hash mutex. */
+ hash_table_t* zip_hash; /*!< hash table of buf_block_t blocks
+ whose frames are allocated to the
+ zip buddy system,
+ indexed by block->frame */
+ ulint n_pend_reads; /*!< number of pending read
+ operations */
+ ulint n_pend_unzip; /*!< number of pending decompressions */
+
+ time_t last_printout_time; // when buf_print_io was last time called
+ buf_buddy_stat_t buddy_stat[BUF_BUDDY_SIZES_MAX + 1];
+ /*!< Statistics of buddy system,
+ indexed by block size */
+ buf_pool_stat_t stat; /*!< current statistics */
+ buf_pool_stat_t old_stat; /*!< old statistics */
+
+ /* @} */
+
+ /** @name Page flushing algorithm fields */
+
+ /* @{ */
+
+ ib_mutex_t flush_list_mutex;/*!< mutex protecting the
+ flush list access. This mutex
+ protects flush_list, flush_rbt
+ and bpage::list pointers when
+ the bpage is on flush_list. It
+ also protects writes to
+ bpage::oldest_modification and
+ flush_list_hp */
+ const buf_page_t* flush_list_hp;/*!< "hazard pointer"
+ used during scan of flush_list
+ while doing flush list batch.
+ Protected by flush_list_mutex */
+ UT_LIST_BASE_NODE_T(buf_page_t) flush_list;
+ /*!< base node of the modified block
+ list */
+ ibool init_flush[BUF_FLUSH_N_TYPES];
+ /*!< this is TRUE when a flush of the
+ given type is being initialized */
+ ulint n_flush[BUF_FLUSH_N_TYPES];
+ /*!< this is the number of pending
+ writes in the given flush type */
+ os_event_t no_flush[BUF_FLUSH_N_TYPES];
+ /*!< this is in the set state
+ when there is no flush batch
+ of the given type running */
+ ib_rbt_t* flush_rbt; /*!< a red-black tree is used
+ exclusively during recovery to
+ speed up insertions in the
+ flush_list. This tree contains
+ blocks in order of
+ oldest_modification LSN and is
+ kept in sync with the
+ flush_list.
+ Each member of the tree MUST
+ also be on the flush_list.
+ This tree is relevant only in
+ recovery and is set to NULL
+ once the recovery is over.
+ Protected by flush_list_mutex */
+ ulint freed_page_clock;/*!< a sequence number used
+ to count the number of buffer
+ blocks removed from the end of
+ the LRU list; NOTE that this
+ counter may wrap around at 4
+ billion! A thread is allowed
+ to read this for heuristic
+ purposes without holding any
+ mutex or latch */
+ ibool try_LRU_scan; /*!< Set to FALSE when an LRU
+ scan for free block fails. This
+ flag is used to avoid repeated
+ scans of LRU list when we know
+ that there is no free block
+ available in the scan depth for
+ eviction. Set to TRUE whenever
+ we flush a batch from the
+ buffer pool. Protected by the
+ buf_pool->mutex */
+ /* @} */
+
+ /** @name LRU replacement algorithm fields */
+ /* @{ */
+
+ UT_LIST_BASE_NODE_T(buf_page_t) free;
+ /*!< base node of the free
+ block list */
+
+ UT_LIST_BASE_NODE_T(buf_page_t) withdraw;
+ /*!< base node of the withdraw
+ block list. It is only used during
+ shrinking buffer pool size, not to
+ reuse the blocks will be removed */
+
+ ulint withdraw_target;/*!< target length of withdraw
+ block list, when withdrawing */
+
+ /** "hazard pointer" used during scan of LRU while doing
+ LRU list batch. Protected by buf_pool::mutex */
+ LRUHp lru_hp;
+
+ /** Iterator used to scan the LRU list when searching for
+ replacable victim. Protected by buf_pool::mutex. */
+ LRUItr lru_scan_itr;
+
+ /** Iterator used to scan the LRU list when searching for
+ single page flushing victim. Protected by buf_pool::mutex. */
+ LRUItr single_scan_itr;
+
+ UT_LIST_BASE_NODE_T(buf_page_t) LRU;
+ /*!< base node of the LRU list */
+
+ buf_page_t* LRU_old; /*!< pointer to the about
+ LRU_old_ratio/BUF_LRU_OLD_RATIO_DIV
+ oldest blocks in the LRU list;
+ NULL if LRU length less than
+ BUF_LRU_OLD_MIN_LEN;
+ NOTE: when LRU_old != NULL, its length
+ should always equal LRU_old_len */
+ ulint LRU_old_len; /*!< length of the LRU list from
+ the block to which LRU_old points
+ onward, including that block;
+ see buf0lru.cc for the restrictions
+ on this value; 0 if LRU_old == NULL;
+ NOTE: LRU_old_len must be adjusted
+ whenever LRU_old shrinks or grows! */
+
+ UT_LIST_BASE_NODE_T(buf_block_t) unzip_LRU;
+ /*!< base node of the
+ unzip_LRU list */
+
+ /* @} */
+ /** @name Buddy allocator fields
+ The buddy allocator is used for allocating compressed page
+ frames and buf_page_t descriptors of blocks that exist
+ in the buffer pool only in compressed form. */
+ /* @{ */
+#if defined UNIV_DEBUG || defined UNIV_BUF_DEBUG
+ UT_LIST_BASE_NODE_T(buf_page_t) zip_clean;
+ /*!< unmodified compressed pages */
+#endif /* UNIV_DEBUG || UNIV_BUF_DEBUG */
+
+ UT_LIST_BASE_NODE_T(buf_buddy_free_t) zip_free[BUF_BUDDY_SIZES_MAX];
+ /*!< buddy free lists */
+
+ buf_page_t* watch; ?????TODODO:到底是干吗的
+ /*!< Sentinel records for buffer
+ pool watches. Protected by
+ buf_pool->mutex. */
+};
+
+
+
+
+struct buf_chunk_t{ // include/buf0buf.ic
+ ulint mem_size; /*!< allocated size of the chunk */
+ ulint size; /*!< size of frames[] and blocks[] */
+ void* mem; /*!< pointer to the memory area which
+ was allocated for the frames */
+ buf_block_t* blocks; /*!< array of buffer control blocks */
+};
+{% endhighlight %}
+
+
+
+
+## Buffer Pool
+
+
+
+
+
+### 预读策略
+
+预读其实是基于这样的判断,在读取磁盘数据时,接下来的请求,有很大可能要读取周围的数据;为此,InnoDB 会异步读取该页周围的数据,从而减小 IO 。
+
+InnoDB 提供了顺序和随机两种策略。
+
+#### 顺序预读
+
+
+
+
+
+
+### 源码解析
+
+Buffer Pool 的初始化入口在 ```innobase_start_or_create_for_mysql()``` 函数中,调用流程如下。
+
+{% highlight text %}
+buf_pool_init() # 初始化buffer pool主函数入口
+ |-buf_pool_init_instance() # 1.1 每个buf-pool大小为srv_buf_pool_size/instance,通过该函数初始化各个实例
+ | |-UT_LIST_INIT(buf_pool->free) # 2.1 初始化buf pool的free list
+ | |
+ | |-buf_chunk_init() # 初始化buffer pool中的chunk
+ | | |-ut_2pow_round() # 2.2 计算每个buf pool实际所需空间,空间必须按照page_size对齐
+ | | |-ut_2pow_round() # 必须为每个page分配一个内存block结构,用于管理内存page
+ | | |-os_mem_alloc_large() # 2.3 为buf pool分配大块内存,对于不同系统配置,调用不同函数
+ | | | |-shmget() shmat() shmct() # 若用了大页(Huge Page),则使用这些方法分配空间
+ | | | |-VirtualAlloc() # Win平台使用该函数分配空间
+ | | | |-ut_malloc_low() # 若未使用MMAP,则调用该函数分配空间
+ | | | |-mmap() # 若使用MMAP,则调用mmap函数分配空间
+ | | |-ut_align() # 2.4 将分配出来的mem空间,按page size对齐,作为page的起点
+ | | |-... ... # 2.5 将mem划分为两部分:前部分作为block结构空间;后部分作为page空间
+ | | | # page空间每一个page的起始位置,必须按照page size对齐
+ | | | # block结构起始位置为mem空间的0号位置
+ | | | # page空间的起始位置,为预留足够的block结构之后的第一个frame位置
+ | | |-buf_block_init() # 2.6 为每个page指定一个block头结构,并初始化各种mutex与lock
+ | | |-UT_LIST_ADD_LAST() # 2.7 将page加入buf pool的free list链表,等待分配
+ | |-hash_create() # 2.8 创建相应的hash表,若page对应于文件中的一个页,则在page hash表中存在
+ | # 便于page在内存中的快速定位
+ |
+ |-buf_pool_set_sizes() # 1.2 设置大小参数
+ |-buf_LRU_old_ratio_update() # 1.3 设置buf_pool.LRU中oldList部分所占的比率,默认为3/8
+ |-btr_search_sys_create()
+{% endhighlight %}
+
+其中比较重要的参数是 innodb_old_blocks_pct,该参数运行时可以调整,初始值为 3/8 。
+
+若设置不同的 old ratio,则涉及到调整 buf_pool->LRU_old 指向的位置,LRU_old 指向的是 LRU list 中位于 old ratio 处的 block 位置,也就是说 old ration 调整,LRU_old 需要相应的做出调整。
+
+### 页面管理
+
+在 MySQL 5.5 之后,InnoDB 支持多 Buffer Pool Instance,内存中有多个 buffer pool 管理,如果此时指定一个 page,需要确定属于哪个 buffer pool 的。
+
+其入参是给定一个 page 的 tablespace id 与 pageno,然后可以定位到对应的管理 buffer pool 。
+
+{% highlight c %}
+class page_id_t {
+private:
+ ib_uint32_t m_space; // Tablespace id
+ ib_uint32_t m_page_no; // Page number
+ mutable ulint m_fold; // 通过m_space+m_page_no计算的hash值
+};
+
+buf_pool_t* buf_pool_get(const page_id_t& page_id)
+{
+ // 每次读取64Pages,BUF_READ_AHEAD_AREA,这64页统一在一个BP实例中管理
+ ulint ignored_page_no = page_id.page_no() >> 6;
+ page_id_t id(page_id.space(), ignored_page_no); // 创建一个实例,无它
+ ulint i = id.fold() % srv_buf_pool_instances; // fold是计算一个hash值
+ return(&buf_pool_ptr[i]);
+}
+{% endhighlight %}
+
+因为目前 InnoDB 的一个 read ahead 是 64 个 page,因此右移 6 位能够保证一个 read ahead area 中的页,都属于同一个 buffer pool 管理。
+
+
+### Buffer Pool LRU List
+
+如上所述,InnoDB Buffer Pool 会通过 LRU 算法作为管理页面的替换策略,将 LRU List 划分为两部分:LRU_young 与 LRU_old,参数的占比可以通过 ```innodb_old_blocks_pct``` 参数设置,默认是 37,也就是 LRU_old 为链表长度的 3/8 。
+
+#### old-page 移动到 new-page
+
+在页面读取时 (get/read ahead),会先将页链入到 LRU_old 的头部;当页面第一次访问时 (read/write),从 LRU_old 链表移动到 LRU_young 的头部,也就是整个 LRU 链表头。
+
+
+
+会通过如下函数进行判断是否要写入到 LRU_head 中。
+
+{% highlight text %}
+buf_page_make_young_if_needed()
+ |-buf_page_peek_if_too_old()
+ |-buf_page_make_young()
+ |-buf_LRU_make_block_young()
+ |-buf_LRU_remove_block()
+ |-buf_LRU_add_block_low()
+{% endhighlight %}
+
+其中判断是否需要写入 LRU_head 的处理如下。
+
+{% highlight c %}
+ibool buf_page_peek_if_too_old(const buf_page_t* bpage)
+{
+ buf_pool_t* buf_pool = buf_pool_from_bpage(bpage);
+
+ if (buf_pool->freed_page_clock == 0) {
+ /* 当前BP还没有淘汰任何页,说明在内存中还有足够的页,那么就不需要更新统计值,
+ 也不需要在LRU链表中移动;通常在warm-up阶段。 */
+ return(FALSE);
+ } else if (buf_LRU_old_threshold_ms && bpage->old) {
+ unsigned access_time = buf_page_is_accessed(bpage); // 直接返回bpage->access_time
+ /* 若当前页已经被访问过,那么会计算距上次访问的时间间隔是否超过了用户设置的
+ innodb_old_blocks_time参数,如果未超过,则不会将页移动到LRU_head。
+ PS. 这里有个整数溢出的情况,通常是50天,也就是如果页面据上次访问超过了50天,
+ 紧接着在此访问时,可能会判断没有超过阈值,从而没有移动到LRU_head。 */
+ if (access_time > 0
+ && ((ib_uint32_t) (ut_time_ms() - access_time))
+ >= buf_LRU_old_threshold_ms) {
+ return(TRUE);
+ }
+ buf_pool->stat.n_pages_not_made_young++;
+ return(FALSE);
+ } else {
+ /* 此时,已经有页面淘汰过,也就是内存中的页已经不够用;但没有设置old_blocks_time参数,
+ 或者该页第一次被访问,那么就通过如下算法判断是否需要移动到LRU_head。*/
+ return(!buf_page_peek_if_young(bpage));
+ }
+}
+
+ibool buf_page_peek_if_young(const buf_page_t* bpage)
+{
+ buf_pool_t* buf_pool = buf_pool_from_bpage(bpage);
+
+ /* 判断当前页是否足够新(Most Recently Update, MRU),而不需要移动
+ buf_pool->freed_page_clock: 该BP实例一共淘汰了多少页
+ bpage->freed_page_clock : 该页最近一次移动到LRU_head时,对应BP中该参数的取值
+ buf_pool->curr_size : 该BP当前使用的页面数量
+ BUF_LRU_OLD_RATIO_DIV : 宏定义为1024
+ buf_pool->LRU_old_ratio : old LRU的占比,默认3/8时,该值为378
+ 公式意义解释:
+ 从该页最近一次移动到BP的LRU_head以来,BP在此期间淘汰的页数量,超过了LRU_yound列表长度
+ 的1/4,那么说明该页已经不够年轻,该页需要移动到LRU_head;否则该页属于MRU,不需要移动。
+ */
+ return((buf_pool->freed_page_clock & ((1UL << 31) - 1))
+ < ((ulint) bpage->freed_page_clock
+ + (buf_pool->curr_size
+ * (BUF_LRU_OLD_RATIO_DIV - buf_pool->LRU_old_ratio)
+ / (BUF_LRU_OLD_RATIO_DIV * 4))));
+}
+{% endhighlight %}
+
+#### new-page 移动到 old-page
+
+正常来说,每次访问页面时,都有可能会将该页移动到 LRU_head 中 (make young),随着越来越多的页加入 LRU_head,那么原来在 LRU list 中的页也就慢慢退化到 LRU list 的 LRU_old list 部分。
+
+实际上,在 ```class buf_page_t``` 类中有个成员变量 old 用来标示是否在 LRU 的 old list 中;该变量的设置基本是通过 ```buf_page_set_old()``` 函数进行设置。
+
+##### 第一次读取
+
+在第一读入到 BP 时,会加入到 LRU 链表的 LRU_old 头部,调用堆栈如下。
+
+{% highlight text %}
+buf_page_init_for_read()
+ |-buf_LRU_add_block(bpage, TRUE) 参数TRUE表示添加到old-list
+ |-buf_LRU_add_block_low()
+{% endhighlight %}
+
+当入参为 TRUE 时,会将页放到 LRU 的 old list 中,否则放在链表头部;当然,如果 LRU 链表比较小,那么会直接忽略该参数,将页放在头部。
+
+{% highlight text %}
+void buf_LRU_add_block_low(buf_page_t* bpage, ibool old)
+{
+ buf_pool_t* buf_pool = buf_pool_from_bpage(bpage);
+
+ if (!old || (UT_LIST_GET_LEN(buf_pool->LRU) < BUF_LRU_OLD_MIN_LEN)) {
+
+ UT_LIST_ADD_FIRST(buf_pool->LRU, bpage);
+
+ bpage->freed_page_clock = buf_pool->freed_page_clock;
+ } else {
+#ifdef UNIV_LRU_DEBUG
+ /* buf_pool->LRU_old must be the first item in the LRU list
+ whose "old" flag is set. */
+ ut_a(buf_pool->LRU_old->old);
+ ut_a(!UT_LIST_GET_PREV(LRU, buf_pool->LRU_old)
+ || !UT_LIST_GET_PREV(LRU, buf_pool->LRU_old)->old);
+ ut_a(!UT_LIST_GET_NEXT(LRU, buf_pool->LRU_old)
+ || UT_LIST_GET_NEXT(LRU, buf_pool->LRU_old)->old);
+#endif /* UNIV_LRU_DEBUG */
+ UT_LIST_INSERT_AFTER(buf_pool->LRU, buf_pool->LRU_old,
+ bpage);
+
+ buf_pool->LRU_old_len++;
+ }
+
+ ut_d(bpage->in_LRU_list = TRUE);
+
+ incr_LRU_size_in_bytes(bpage, buf_pool);
+
+ if (UT_LIST_GET_LEN(buf_pool->LRU) > BUF_LRU_OLD_MIN_LEN) {
+
+ ut_ad(buf_pool->LRU_old);
+
+ /* Adjust the length of the old block list if necessary */
+
+ buf_page_set_old(bpage, old);
+ buf_LRU_old_adjust_len(buf_pool);
+
+ } else if (UT_LIST_GET_LEN(buf_pool->LRU) == BUF_LRU_OLD_MIN_LEN) {
+
+ /* The LRU list is now long enough for LRU_old to become
+ defined: init it */
+
+ buf_LRU_old_init(buf_pool);
+ } else {
+ buf_page_set_old(bpage, buf_pool->LRU_old != NULL);
+ }
+
+ /* If this is a zipped block with decompressed frame as well
+ then put it on the unzip_LRU list */
+ if (buf_page_belongs_to_unzip_LRU(bpage)) {
+ buf_unzip_LRU_add_block((buf_block_t*) bpage, old);
+ }
+}
+{% endhighlight %}
+
+
+### 其它
+
+#### innodb_old_blocks_pct
+
+该变量会通过 buf_LRU_old_ratio_update_instance() 函数进行更新,默认 37,范围是 5~95 。
+
+{% highlight c %}
+#define BUF_LRU_OLD_RATIO_DIV 1024
+#define BUF_LRU_OLD_RATIO_MAX BUF_LRU_OLD_RATIO_DIV
+#define BUF_LRU_OLD_RATIO_MIN 51
+
+uint buf_LRU_old_ratio_update_instance(buf_pool_t* buf_pool, uint old_pct, ibool adjust)
+{
+ uint ratio;
+
+ ratio = old_pct * BUF_LRU_OLD_RATIO_DIV / 100;
+ if (ratio < BUF_LRU_OLD_RATIO_MIN) {
+ ratio = BUF_LRU_OLD_RATIO_MIN;
+ } else if (ratio > BUF_LRU_OLD_RATIO_MAX) {
+ ratio = BUF_LRU_OLD_RATIO_MAX;
+ }
+
+ if (adjust) {
+ buf_pool_mutex_enter(buf_pool);
+
+ if (ratio != buf_pool->LRU_old_ratio) {
+ buf_pool->LRU_old_ratio = ratio;
+
+ if (UT_LIST_GET_LEN(buf_pool->LRU)
+ >= BUF_LRU_OLD_MIN_LEN) {
+
+ buf_LRU_old_adjust_len(buf_pool);
+ }
+ }
+
+ buf_pool_mutex_exit(buf_pool);
+ } else {
+ buf_pool->LRU_old_ratio = ratio;
+ }
+ /* the reverse of
+ ratio = old_pct * BUF_LRU_OLD_RATIO_DIV / 100 */
+ return((uint) (ratio * 100 / (double) BUF_LRU_OLD_RATIO_DIV + 0.5));
+}
+{% endhighlight %}
+
+如上,在计算时,实际上会乘以 BUF_LRU_OLD_RATIO_DIV ,然后再除以 100,也就意味着在内部使用时会大值乘于 10 。
+
+
+### 读取
+
+接下来看看如何读取一个页,其中实际分配缓存是在 buf_LRU_get_free_block() 函数中。这一函数是在用户线程中调用,而且只会从 free_list 中获取空闲页,即使是从 LRU_list 上获取的,也需要先将 LRU_list 中的页保存在 free_list 中。
+
+尝试从 BP 中获取一个 block,大多数情况下 free_list 上是有足够的空闲页,因此可以直接分配,分配完之后,直接从 free_list 上删除。
+
+如果 free_list 上没有空闲页了,那么尝试从 LRU_list 上分配,一般会经历如下循环。
+
+{% highlight text %}
+* iteration 0:
+ * get a block from free list, success:done
+ * if buf_pool->try_LRU_scan is set
+ * scan LRU up to srv_LRU_scan_depth to find a clean block
+ * the above will put the block on free list
+ * success:retry the free list
+ * flush one dirty page from tail of LRU to disk
+ * the above will put the block on free list
+ * success: retry the free list
+* iteration 1:
+ * same as iteration 0 except:
+ * scan whole LRU list
+ * scan LRU list even if buf_pool->try_LRU_scan is not set
+* iteration > 1:
+ * same as iteration 1 but sleep 10ms
+@return the free control block, in state BUF_BLOCK_READY_FOR_USE */
+{% endhighlight %}
+
+
+
+{% highlight text %}
+buf_page_get_gen() 获取数据库中的页
+ |-buf_pool_get() 所在buffer pool实例
+ |-buf_page_hash_lock_get() 获取锁
+ |-rw_lock_s_lock() 加锁
+ |-buf_page_hash_get_low() 尝试从bp中获取页
+ |-buf_pool_watch_is_sentinel() 干吗的???
+ |-buf_read_page()
+ | |-buf_read_page_low()
+ | |-buf_page_init_for_read() 初始化bp
+ | |-buf_LRU_get_free_block() 如果没有压缩,则直接获取空闲页
+ | |-buf_LRU_get_free_only() 尝试从free_list获取,有则直接返回即可
+ | |-buf_LRU_scan_and_free_block()
+ | |-buf_LRU_free_from_unzip_LRU_list()
+ | |-buf_LRU_free_from_common_LRU_list()
+ | |-buf_LRU_add_block()
+ | |
+ | |-buf_buddy_alloc() 压缩页,使用buddy系统
+ |
+ |-buf_read_ahead_random() 如果需要做随机预读
+
+
+ |-fil_io()
+ |-buf_block_get_state() 根据页的类型,判断是否需要进一步处理,如ZIP
+
+buf_read_ahead_linear()
+{% endhighlight %}
+
+## 参考
+
+关于 LRU list 被划分为 new 与 old 两部分的原因及意义,可参考 [InnoDB Buffer Pool](http://dev.mysql.com/doc/refman/en/innodb-buffer-pool.html) 。
+
+
+
+
+
+
+{% highlight text %}
+{% endhighlight %}
diff --git a/_drafts/2015-06-20-mysql-innodb-insert-buffer_init.md b/_drafts/2015-06-20-mysql-innodb-insert-buffer_init.md
new file mode 100644
index 0000000..05eb38a
--- /dev/null
+++ b/_drafts/2015-06-20-mysql-innodb-insert-buffer_init.md
@@ -0,0 +1,14 @@
+---
+title: InnoDB Insert Buffer
+layout: post
+comments: true
+language: chinese
+category: [mysql,database]
+---
+
+
+
+
+
+{% highlight text %}
+{% endhighlight %}
diff --git a/_drafts/2015-06-26-python-datetime_init.md b/_drafts/2015-06-26-python-datetime_init.md
new file mode 100644
index 0000000..db83582
--- /dev/null
+++ b/_drafts/2015-06-26-python-datetime_init.md
@@ -0,0 +1,347 @@
+---
+title: Python 时间操作
+layout: post
+comments: true
+category: [program, python]
+language: chinese
+---
+
+
+
+
+Python 与时间处理相关的提供了 datetime、time、calendar 三个模块,而且还有三方模块 pytz 可以使用;另外,datetime 模块中又存在 datetime、time 类,不要与相应的模块混淆。
+
+{% highlight text %}
+datetime.date 理想化的日期对象,有年月日三个属性
+datetime.time 理想化的时间对象,不考虑闰秒,有时分秒微秒和时区五个属性
+datetime.datetime 上述两个对象的组合
+datetime.timedelta 可用于上述三个对象的差值
+datetime.tzinfo 时区信息
+
+time 各种时间操作转换的方法
+calendar 提供日历相关的方法
+pytz 使用Olson TZ Database解决时区、夏令时等相关的计算问题
+{% endhighlight %}
+
+
+简单来说,在 Pyton 中,与时间相关的时间类型主要有如下的四种。
+
+{% highlight python %}
+import time, datetime
+
+#----- 1. time string 用于打印输出的字符串
+time.ctime() # 'Fri June 26 21:02:55 2015'
+
+#----- 2. datetime tuple 包含了年月日-时分秒微妙-时区信息
+datetime.datetime.now() # datetime.datetime(2012, 12, 17, 21, 3, 44, 139715)
+
+#----- 3. time tuple 也就是time.struct_time对象类型
+datetime.datetime.now().timetuple() # time.struct_time(tm_year=2017, tm_mon=1, tm_mday=2,
+ # tm_hour=10, tm_min=10, tm_sec=48, tm_wday=0, tm_yday=2, tm_isdst=-1)
+
+#----- 4. timestamp 时间戳类型,从1970-01-01 00:00:00 GMT以来的秒数
+time.time() # 1355749338.05917
+{% endhighlight %}
+
+
+
+
+![datetime transfrom]({{ site.url }}/images/python/datetime-transform.jpg "datetime transfrom"){: .pull-center }
+
+## 时间处理
+
+很多与日期、时间的 api 都在 datetime 模块内,其中常见的操作如下。
+
+{% highlight python %}
+import datetime
+
+#----- 1. 日期输出格式化 datetime=>string
+now = datetime.datetime.now()
+now.strftime('%Y-%m-%d %H:%M:%S') # '2015-04-07 19:11:21'
+now.strftime('%a, %d %b %Y %H:%M:%S %Z') # Sun, 01 Jan 2017 02:10:33 GMT
+
+#----- 2. 日期输出格式化 string=>datetime
+t_str = '2015-04-07 19:11:21'
+d = datetime.datetime.strptime(t_str, '%Y-%m-%d %H:%M:%S')
+
+#----- 3. 两个日期相差多少天
+d1 = datetime.datetime.strptime('2015-06-15 18:11:27', '%Y-%m-%d %H:%M:%S')
+d2 = datetime.datetime.strptime('2015-06-02 18:11:27', '%Y-%m-%d %H:%M:%S')
+print delta, delta,days
+
+#----- 4. 今天的n天后的日期
+now = datetime.datetime.now()
+delta = datetime.timedelta(days=3)
+print (now + delta).strftime('%Y-%m-%d %H:%M:%S')
+{% endhighlight %}
+
+其中,时间格式化的参数内容与 man date 相同,可以直接参考。
+
+
+
+
+
+
+## xxx
+
+
+由于国家和地区可以自己选择时区以及是否使用夏令时,所以pytz模块在有需要的情况下得更新自己的时区以及夏令时相关的信息。比如当前pytz版本的OLSON_VERSON = ‘2013g’, 就是包括了Morocco可以使用夏令时。
+如何正确为你所用
+
+不是题外话的题外话,客户端必须正确收集用户的timezone信息。比较常见的一个错误是,保存用户所在时区的偏移值。比如对于中国的时区,保存了+8。这里其实丢失了用户所在的地区(同样的时间偏移,可能对应多个国家或者地区)。而且如果用户所在时区是有夏令时的话,在每年开始和结束夏令时的时候,这个偏移值都是要发生变化的。
+
+
+{% highlight text %}
+>>> import pytz
+>>> pytz.all_timezones # 查看所有的timezone
+[...,'Asia/Shanghai', ...] # 选择上海的时区,也就是东八区
+>>> pytz.timezone('Asia/Shanghai') # 构建tzinfo对象
+
+{% endhighlight %}
+
+
+我们开始要把timezone加入时间的转换里面了。
+
+首先,timestamp和datetime的转换。timestamp,一个数字,表示从UTC时间1970/01/01开始的秒数。
+
+
+
+{% highlight text %}
+>>> import datetime
+>>> datetime.datetime.fromtimestamp(0, pytz.timezone('UTC')) # 含有时区信息
+datetime.datetime(1970, 1, 1, 0, 0, tzinfo=)
+>>> tz = pytz.timezone('Asia/Shanghai') # 上海则早8小时
+>>> datetime.fromtimestamp(0, tz)
+datetime.datetime(1970, 1, 1, 8, 0, tzinfo=)
+{% endhighlight %}
+
+如上,timestamp 是和 UTC 绑定的,不同的时区会有不同的时间偏移。
+
+给定一个timestamp,构建datetime的时候无论传入的是什么时区,对应出来的结果都是同一个时间。 但是python里面这里有个坑。
+
+>>> ts = 1408071830
+>>> dt = datetime.fromtimestamp(ts, tz)
+datetime.datetime(2014, 8, 15, 11, 3, 50, tzinfo=)
+>>> time.mktime(dt.timetuple())
+1408100630.0
+>>> dt.timetuple()
+time.struct_time(tm_year=2014, tm_mon=8, tm_mday=15, tm_hour=11, tm_min=3, tm_sec=50, tm_wday=4, tm_yday=227, tm_isdst=0)
+>>> dt.astimezone(pytz.utc)
+datetime.datetime(2014, 8, 15, 3, 3, 50, tzinfo=)
+>>> time.mktime(dt.astimezone(pytz.utc).timetuple())
+1408071830.0
+
+time模块的mktime方法支持从timetuple取得timestamp,datetime对象可以直接转换成timetuple。这时候直接使用time.mktime(dt.timetuple())看起来就是很自然的获取timestamp方法。但是我们注意到timetuple方法是直接把当前时间的年月日时分秒直接取出来的。所以这个转换过程在timetuple这个方法这一步丢了时区信息。根据timestamp的定义,正确的方法是把datetime对象利用asttimezone显式转换成UTC时间。
+
+第二,datetime和date以及time的关系 datetime模块同时提供了datetime对象,time对象,date对象。他们之间的关系可以从如下代码简单看出来。
+
+>>> d = datetime.date(2014, 8, 20)
+>>> t = datetime.time(11, 30)
+>>> dt = datetime.datetime.combine(d, t)
+datetime.datetime(2014, 8, 20, 11, 30)
+>>> dt.date()
+datetime.date(2014, 8, 20)
+>>> dt.time()
+datetime.time(11, 30)
+
+>>> dt = datetime.datetime.fromtimestamp(1405938446, pytz.timezone('UTC'))
+datetime.datetime(2014, 7, 21, 10, 27, 26, tzinfo=)
+>>> dt.date()
+datetime.date(2014, 7, 21)
+>>> dt.time()
+datetime.time(10, 27, 26)
+>>> dt.timetz()
+datetime.time(10, 27, 26, tzinfo=)
+
+>>> datetime.datetime.combine(dt.date(), dt.time())
+datetime.datetime(2014, 7, 21, 10, 27, 26)
+>>> datetime.datetime.combine(dt.date(), dt.timetz())
+datetime.datetime(2014, 7, 21, 10, 27, 26, tzinfo=)
+
+简单说就是,datetime可以取得date和time对象,datetime和time对象可以带timezone信息。date和time对象可以使用datetime.datetime.combine合并获得datetime对象。
+
+第三,日期的加减 datetime,date对象都可以使用timedelta来进行。
+ 直接看代码
+
+>>> d1 = datetime.datetime(2014, 5, 20)
+>>> d2 = d1+datetime.timedelta(days=1, hours=2)
+>>> d1
+datetime.datetime(2014, 5, 20, 0, 0)
+>>> d2
+datetime.datetime(2014, 5, 21, 2, 0)
+>>> x = d2 - d1
+>>> x
+datetime.timedelta(1, 7200)
+>>> x.seconds
+7200
+>>> x.days
+1
+
+ 第四,如何对datetime对象正确设置timezone信息
+
+先看代码。
+
+>>> ddt1 = datetime.datetime(2014, 8, 20, 10, 0, 0, 0, pytz.timezone('Asia/Shanghai'))
+>>> ddt1
+datetime.datetime(2014, 8, 20, 10, 0, tzinfo=)
+>>> ddt2
+ datetime.datetime(2014, 8, 20, 11, 0)
+
+>>> ddt1.astimezone(pytz.utc)
+datetime.datetime(2014, 8, 20, 1, 54, tzinfo=)
+>>> ddt2.astimezone(pytz.utc)
+ValueError: astimezone() cannot be applied to a naive datetime
+
+>>> tz = timezone('Asia/Shanghai')
+>>> tz.localize(ddt1)
+ValueError: Not naive datetime (tzinfo is already set)
+>>> tz.localize(ddt2)
+datetime.datetime(2014, 8, 20, 11, 0, tzinfo=)
+
+这里抛出来的ValueError,引入了一个naive datetime的概念。简单说naive datetime就是不知道时区信息的datetime对象。没有timezone信息的datetime理论上讲不能定位到具体的时间点。所以对于设定了timezone的datetime对象,可以使用astimezone方法将timezone设定为另一个。对于不包含timezone的datetime对象,使用timezone.localize方法设定timezone。
+
+但是,这里有没有发现一个问题?我们明明设定的是11点整的,使用astimezone之后跑出来个54分是想怎样?
+
+我们注意到,datetime直接传入timezone对象构建出来的带timezone的datetime对象和使用locallize方法构建出来的datetime对象,在打印出来的时候tzinfo显示有所不同,一个是LMT+8:06,一个是CST+8:00,不用说了,54分就搁这来的吧。LMT学名Local Mean Time,用于比较平均日出时间的。有兴趣的可以自己看看Shanghai和Urumqi的LMT时间。CST是China Standard Time,不用解释了。根据pytz的文档,
+
+Unfortunately using the tzinfo argument of the standard datetime constructors ‘’does not work’’ with pytz for many timezones.
+It is safe for timezones without daylight saving transitions though, such as UTC:
+The preferred way of dealing with times is to always work in UTC, converting to localtime only when generating output to be read by humans.
+...
+You can take shortcuts when dealing with the UTC side of timezone conversions. normalize() and localize() are not really necessary when there are no daylight saving time transitions to deal with.
+
+我们按照这个说法再试试看,如下,这回pytz.timezone('Asia/Shanghai’)没有再玩幺蛾子了。
+
+>>> x = datetime.datetime(2014, 8, 20, 10, 0, 0, 0, pytz.utc)
+>>> x
+datetime.datetime(2014, 8, 20, 10, 0, tzinfo=)
+>>> x.astimezone(pytz.timezone('Asia/Shanghai'))
+datetime.datetime(2014, 8, 20, 18, 0, tzinfo=)
+
+所以最保险的方法是使用locallize方法构造带时区的时间。
+
+顺带说下,里面提到了normalize是用来校正计算的时间跨越DST切换的时候出错的情况,还是参见文档,关键部分摘录如下:
+
+This library differs from the documented Python API for tzinfo implementations; if you want to create local wallclock times you need to use the localize() method documented in this document. In addition, if you perform date arithmetic on local times that cross DST boundaries, the result may be in an incorrect timezone (ie. subtract 1 minute from 2002-10-27 1:00 EST and you get 2002-10-27 0:59 EST instead of the correct 2002-10-27 1:59 EDT). A normalize() method is provided to correct this. Unfortunately these issues cannot be resolved without modifying the Python datetime implementation (see PEP-431).
+
+ 回到最初的问题,我程序需要给用户两天后的21点发送通知,这个时间怎么计算?
+
+>>> import pytz
+>>> import time
+>>> import datetime
+>>> tz = pytz.timezone('Asia/Shanghai')
+>>> user_ts = int(time.time())
+>>> d1 = datetime.datetime.fromtimestamp(user_ts)
+>>> d1x = tz.localize(d1)
+>>> d1x
+datetime.datetime(2015, 5, 26, 1, 43, 41, tzinfo=)
+
+>>> d2 = d1x + datetime.timedelta(days=2)
+>>> d2
+datetime.datetime(2015, 5, 28, 1, 43, 41, tzinfo=)
+>>> d2.replace(hour=21, minute=0)
+>>> d2
+datetime.datetime(2015, 5, 28, 21, 0, 41, tzinfo=)
+
+基本步骤为,根据时间戳和时区信息构建正确的时间d1x,使用timedelta进行对时间进行加减操作,使用replace方法替换小时等信息。
+
+总结,基本上时间相关的这些方法,大部分你都可以直接按照自己的需要封装到一个独立的utility模块,然后就不需要再去管它了。你要做的是,至少有一个人先正确地管一下。
+
+
+
+
+
+
+http://tech.glowing.com/cn/dealing-with-timezone-in-python/
+
+{% highlight python %}
+{% endhighlight %}
diff --git a/_drafts/2015-07-12-javascript-dom_init.md b/_drafts/2015-07-12-javascript-dom_init.md
new file mode 100644
index 0000000..63651f1
--- /dev/null
+++ b/_drafts/2015-07-12-javascript-dom_init.md
@@ -0,0 +1,561 @@
+---
+Date: October 19, 2013
+title: Javascript DOM 操作
+layout: post
+comments: true
+language: chinese
+category: [misc]
+---
+
+
+
+
+# 简介
+
+通过 JavaScript,添加、移除、改变或重排页面上的项目,甚至可以重构整个 HTML 文档。如果要改变某个元素,就需要有一个对 HTML 文档中所有元素进行访问的入口,这个入口都是通过 DOM 操作的。
+
+根据 DOM,HTML 文档中的每个成分都是一个节点:A) 整个文档是一个文档节点;B) 每个 HTML 标签是一个元素节点;C) 包含在 HTML 元素中的文本是文本节点;D) 每一个 HTML 属性是一个属性节点;E) 注释属于注释节点。
+
+各个节点之间彼此都有等级关系,HTML 文档中的所有节点组成了一个文档树或节点树。
+
+![DOM tree]({{ site.url }}/images/webserver/dom-tree.jpg){: .pull-center width="500"}
+
+
+# Javascript 操作 DOM
+
+## 获取节点元素
+
+### getElementById()
+
+根据 ID 获取元素节点 。
+
+{% highlight html %}
+
+
+
+{% endhighlight %}
+
+
+
+
+
+
+
+
+
+# 参考
+
+如上的示例可以直接参考 [javascript-dom](/reference/webserver/javascript-dom.html) 。
+
+另外可以参考一个不错的调试工具 [10分钟学会前端调试利器——FireBug](http://www.imooc.com/article/3012),或者 [本地文档](/reference/javascript/10分钟学会前端调试利器.mht) 。
+
+
+
+
+{% highlight html %}
+{% endhighlight %}
diff --git a/_drafts/2015-07-13-mysql-partition_init.md b/_drafts/2015-07-13-mysql-partition_init.md
new file mode 100644
index 0000000..0060430
--- /dev/null
+++ b/_drafts/2015-07-13-mysql-partition_init.md
@@ -0,0 +1,429 @@
+---
+title: MySQL 分区表
+layout: post
+comments: true
+language: chinese
+category: [mysql,database]
+keywords: mysql,plugin,插件
+description: 在 MySQL 中,为了提高其灵活性,很多的功能都是通过插件来实现的,常见的比如 semi-sync、存储引擎、登陆认证等等。因为 MySQL 是 C/C++ 实现的,对于插件来说实际为动态链接库,保存在 plugin_dir 变量对应的目录下。在此介绍一下 MySQL 的插件实现。
+---
+
+
+
+
+
+= 水平分区(根据列属性按行分)=
+举个简单例子:一个包含十年发票记录的表可以被分区为十个不同的分区,每个分区包含的是其中一年的记录。
+
+=== 水平分区的几种模式:===
+* Hash(哈希) – 这中模式允许DBA通过对表的一个或多个列的Hash Key进行计算,最后通过这个Hash码不同数值对应的数据区域进行分区,。例如DBA可以建立一个对表主键进行分区的表。
+* Key(键值) – 上面Hash模式的一种延伸,这里的Hash Key是MySQL系统产生的。
+* List(预定义列表) – 这种模式允许系统通过DBA定义的列表的值所对应的行数据进行分割。例如:DBA建立了一个横跨三个分区的表,分别根据2004年2005年和2006年值所对应的数据。
+* Composite(复合模式) - 很神秘吧,哈哈,其实是以上模式的组合使用而已,就不解释了。举例:在初始化已经进行了Range范围分区的表上,我们可以对其中一个分区再进行hash哈希分区。
+= 垂直分区(按列分)=
+举个简单例子:一个包含了大text和BLOB列的表,这些text和BLOB列又不经常被访问,这时候就要把这些不经常使用的text和BLOB了划分到另一个分区,在保证它们数据相关性的同时还能提高访问速度。
+
+
+
+
+{% highlight sql %}
+CREATE TABLE foobar_partition (
+ c1 INT DEFAULT NULL,
+ c2 VARCHAR(30) DEFAULT NULL,
+ c3 DATE DEFAULT NULL
+) ENGINE=InnoDB PARTITION BY RANGE (year(c3)) (
+ PARTITION p0 VALUES LESS THAN (1995),
+ PARTITION p1 VALUES LESS THAN (1996) ,
+ PARTITION p2 VALUES LESS THAN (1997) ,
+ PARTITION p3 VALUES LESS THAN (1998) ,
+ PARTITION p4 VALUES LESS THAN (1999) ,
+ PARTITION p5 VALUES LESS THAN (2000) ,
+ PARTITION p6 VALUES LESS THAN (2001) ,
+ PARTITION p7 VALUES LESS THAN (2002) ,
+ PARTITION p8 VALUES LESS THAN (2003) ,
+ PARTITION p9 VALUES LESS THAN (2004) ,
+ PARTITION p10 VALUES LESS THAN (2010),
+ PARTITION p11 VALUES LESS THAN MAXVALUE
+);
+CREATE TABLE foobar_normal (
+ c1 INT DEFAULT NULL,
+ c2 VARCHAR(30) DEFAULT NULL,
+ c3 DATE DEFAULT NULL
+) ENGINE=InnoDB;
+
+TRUNCATE TABLE load_partition;
+DELIMITER EOF
+drop procedure if exists `test`.`load_partition` EOF
+CREATE PROCEDURE load_partition(IN number INT(11))
+ BEGIN
+ DECLARE i INT DEFAULT 1;
+ -- such as 1-200,200-400,....
+ WHILE i <= number DO
+ IF mod(i, 20)=1 THEN
+ SET @sqltext=concat("(", i, ", '", concat('testing partitions ', i), "', '",
+ adddate('1995-01-01',(rand(i)*36520) mod 3652), "')");
+ ELSEIF mod(i, 20)=0 THEN
+ SET @sqltext=concat(@sqltext, ",(", i, ", '", concat('testing partitions ', i), "', '",
+ adddate('1995-01-01',(rand(i)*36520) mod 3652), "')");
+ SET @sqltext=concat("INSERT INTO foobar_partition VALUES", @sqltext);
+ -- SELECT @sqltext;
+ SELECT i AS count;
+ PREPARE stmt FROM @sqltext;
+ EXECUTE stmt;
+ DEALLOCATE PREPARE stmt;
+ SET @sqltext='';
+ ELSE
+ SET @sqltext=concat(@sqltext, ",(", i, ", '", concat('testing partitions ', i), "', '",
+ adddate('1995-01-01',(rand(i)*36520) mod 3652), "')");
+ END IF;
+ SET i = i + 1;
+ END WHILE;
+ -- process when number is not be moded by 2000, such as 201,402,1520,...
+ IF @sqltext<>'' THEN
+ SET @sqltext=concat("INSERT INTO foobar_partition VALUES", @sqltext);
+ -- SELECT @sqltext;
+ PREPARE stmt FROM @sqltext;
+ EXECUTE stmt;
+ DEALLOCATE PREPARE stmt;
+ SET @sqltext='';
+ END IF;
+ END
+EOF
+DELIMITER ;
+CALL load_partition(80);
+{% endhighlight %}
+
+MySQL 中,可以将分区设置为独立的数据、索引存放目录,同时,这些目录所在的物理磁盘分区可能也都是完全独立的,从而可以提高磁盘 IO 吞吐量;需要注意的是 MERGE, CSV, FEDERATED 存储引擎不支持分区。
+
+### RANGE/RANGE COLUMNS
+
+可以将数据划分不同的区间,这些区间要连续且不能重叠,使用 ```VALUES LESS THAN``` 操作符定义分区;例如,将一个表通过年份划分成三个分区,80 年代 (1980's) 的数据,90 年代 (1990's) 的数据以及任何在 2000 年 (包括2000年) 后的数据。
+
+RANGE 分区主要是基于整数的分区,对于非整形的字段需要利用表达式将其转换成整形。
+
+{% highlight text %}
+CREATE TABLE users (
+ uid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(30) NOT NULL DEFAULT '',
+ email VARCHAR(30) NOT NULL DEFAULT ''
+) PARTITION BY RANGE (uid) (
+ PARTITION p0 VALUES LESS THAN (3000000)
+ DATA DIRECTORY = '/data0/data'
+ INDEX DIRECTORY = '/data1/idx',
+
+ PARTITION p1 VALUES LESS THAN (6000000)
+ DATA DIRECTORY = '/data2/data'
+ INDEX DIRECTORY = '/data3/idx',
+
+ PARTITION p2 VALUES LESS THAN (9000000)
+ DATA DIRECTORY = '/data4/data'
+ INDEX DIRECTORY = '/data5/idx',
+
+ PARTITION p3 VALUES LESS THAN MAXVALUE 必须
+ DATA DIRECTORY = '/data6/data'
+ INDEX DIRECTORY = '/data7/idx'
+);
+{% endhighlight %}
+
+
+
+MySQL 5.5 改进 range 分区,提供 range columns 分区支持非整数分区。
+
+COLUMNS分区:可以无需通过表达式进行转换直接对非整形字段进行分区,同时COLUMNS分区还支持多个字段组合分区,只有RANGELIST存在COLUMNS分区,COLUMNS是RANGE和LIST分区的升级
+RANGE COLUMNS是RANGE分区的一种特殊类型,它与RANGE分区的区别如下:
+1. RANGE COLUMNS不接受表达式,只能是列名。而RANGE分区则要求分区的对象是整数。
+2. RANGE COLUMNS允许多个列,在底层实现上,它比较的是元祖(多个列值组成的列表),而RANGE比较的是标量,即数值的大小。
+3. RANGE COLUMNS不限于整数对象,date,datetime,string都可作为分区列。
+同RANGE分区类似,它的区间范围必须是递增的,有时候,列涉及的太多,不好判断区间的大小,可采用下面的方式进行判断:
+SELECT (5,10) < (5,12), (5,11) < (5,12), (5,12) < (5,12);
+
+
+### LIST/LIST COLUMNS
+
+每个分区的定义和选择是基于某列的值或者一个返回整数的表达式,是基于列出的枚举值列表进行分区。
+
+CREATE TABLE category (
+ cid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(30) NOT NULL DEFAULT ''
+)
+PARTITION BY LIST (cid) (
+ PARTITION p0 VALUES IN (0,4,8,12)
+ DATA DIRECTORY = '/data0/data'
+ INDEX DIRECTORY = '/data1/idx',
+
+ PARTITION p1 VALUES IN (1,5,9,13)
+ DATA DIRECTORY = '/data2/data'
+ INDEX DIRECTORY = '/data3/idx',
+
+ PARTITION p2 VALUES IN (2,6,10,14)
+ DATA DIRECTORY = '/data4/data'
+ INDEX DIRECTORY = '/data5/idx',
+
+ PARTITION p3 VALUES IN (3,7,11,15)
+ DATA DIRECTORY = '/data6/data'
+ INDEX DIRECTORY = '/data7/idx'
+);
+LIST COLUMNS分区同样是LIST分区的一种特殊类型,它和RANGE COLUMNS分区较为相似,同样不接受表达式,同样支持多个列支持string,date和datetime类型。
+CREATE TABLE customers_2 (
+ first_name VARCHAR(25),
+ last_name VARCHAR(25),
+ street_1 VARCHAR(30),
+ street_2 VARCHAR(30),
+ city VARCHAR(15),
+ renewal DATE
+)
+PARTITION BY LIST COLUMNS(city,last_name,first_name) (
+ PARTITION pRegion_1 VALUES IN (('Oskarshamn', 'Högsby', 'Mönsterås'),('Nässjö', 'Eksjö', 'Vetlanda')),
+ PARTITION pRegion_2 VALUES IN(('Vimmerby', 'Hultsfred', 'Västervik'),('Uppvidinge', 'Alvesta', 'Växjo'))
+);
+
+
+### HASH/LINEAR HASH
+
+ TODODO: hash值是如何计算的
+
+基于给定的分区个数,将数据分配到不同的分区,HASH分区只能针对整数进行HASH,对于非整形的字段只能通过表达式将其转换成整数。
+
+CREATE TABLE users (
+ uid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(30) NOT NULL DEFAULT '',
+ email VARCHAR(30) NOT NULL DEFAULT ''
+)
+PARTITION BY HASH (uid) PARTITIONS 4 (
+ PARTITION p0
+ DATA DIRECTORY = '/data0/data'
+ INDEX DIRECTORY = '/data1/idx',
+
+ PARTITION p1
+ DATA DIRECTORY = '/data2/data'
+ INDEX DIRECTORY = '/data3/idx',
+
+ PARTITION p2
+ DATA DIRECTORY = '/data4/data'
+ INDEX DIRECTORY = '/data5/idx',
+
+ PARTITION p3
+ DATA DIRECTORY = '/data6/data'
+ INDEX DIRECTORY = '/data7/idx'
+);
+它的优点是在数据量大的场景,譬如TB级,增加、删除、合并和拆分分区会更快,缺点是,相对于HASH分区,它数据分布不均匀的概率更大。
+http://dev.mysql.com/doc/refman/5.6/en/partitioning-linear-hash.html
+CREATE TABLE `test`.`partition_t5`(
+`id` INT UNSIGNED NOT NULL,
+`username` VARCHAR(30) NOT NULL,
+`email` VARCHAR(30) NOT NULL,
+`birth_date` DATE NOT NULL
+) ENGINE=MYISAM
+PARTITION BY LINEAR HASH(id)
+PARTITIONS 5;
+
+### KEY
+
+hash分区允许用户自定义的表达式,而key分区不允许使用用户自定义的表达式。
+
+hash分区只支持整数分区,key分区支持除了blob或text类型之外的其他数据类型分区。
+与hash分区不同,创建key分区表的时候,可以不指定分区键,默认会选择使用主键/唯一键作为分区键,没有主键/唯一键,必须指定分区键。
+KEY分区和HASH分区的算法不一样,PARTITION BY HASH (expr),MOD取值的对象是expr返回的值,而PARTITION BY KEY (column_list),基于的是列的MD5值。
+CREATE TABLE users (
+ uid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(30) NOT NULL DEFAULT '',
+ email VARCHAR(30) NOT NULL DEFAULT ''
+)
+PARTITION BY KEY (uid) PARTITIONS 4 (
+ PARTITION p0
+ DATA DIRECTORY = '/data0/data'
+ INDEX DIRECTORY = '/data1/idx',
+
+ PARTITION p1
+ DATA DIRECTORY = '/data2/data'
+ INDEX DIRECTORY = '/data3/idx',
+
+ PARTITION p2
+ DATA DIRECTORY = '/data4/data'
+ INDEX DIRECTORY = '/data5/idx',
+
+ PARTITION p3
+ DATA DIRECTORY = '/data6/data'
+ INDEX DIRECTORY = '/data7/idx'
+);
+
+
+子分区
+子分区是针对 RANGE/LIST 类型的分区表中每个分区的再次分割。再次分割可以是 HASH/KEY 等类型。例如:
+CREATE TABLE users (
+ uid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(30) NOT NULL DEFAULT '',
+ email VARCHAR(30) NOT NULL DEFAULT ''
+)
+PARTITION BY RANGE (uid) SUBPARTITION BY HASH (uid % 4) SUBPARTITIONS 2(
+ PARTITION p0 VALUES LESS THAN (3000000)
+ DATA DIRECTORY = '/data0/data'
+ INDEX DIRECTORY = '/data1/idx',
+
+ PARTITION p1 VALUES LESS THAN (6000000)
+ DATA DIRECTORY = '/data2/data'
+ INDEX DIRECTORY = '/data3/idx'
+);
+
+对 RANGE 分区再次进行子分区划分,子分区采用 HASH 类型。
+CREATE TABLE users (
+ uid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ name VARCHAR(30) NOT NULL DEFAULT '',
+ email VARCHAR(30) NOT NULL DEFAULT ''
+)
+PARTITION BY RANGE (uid) SUBPARTITION BY KEY(uid) SUBPARTITIONS 2(
+ PARTITION p0 VALUES LESS THAN (3000000)
+ DATA DIRECTORY = '/data0/data'
+ INDEX DIRECTORY = '/data1/idx',
+
+ PARTITION p1 VALUES LESS THAN (6000000)
+ DATA DIRECTORY = '/data2/data'
+ INDEX DIRECTORY = '/data3/idx'
+);
+
+对 RANGE 分区再次进行子分区划分,子分区采用 KEY 类型。
+
+= 分区管理 =
+
+删除分区
+ALERT TABLE users DROP PARTITION p0;
+
+重建分区
+----- RANGE分区重建,将原来的 p0,p1 分区合并起来,放到新的 p0 分区中
+ALTER TABLE users REORGANIZE PARTITION p0,p1 INTO (PARTITION p0 VALUES LESS THAN (6000000));
+----- LIST分区重建,将原来的 p0,p1 分区合并起来,放到新的 p0 分区中。
+ALTER TABLE users REORGANIZE PARTITION p0,p1 INTO (PARTITION p0 VALUES IN(0,1,4,5,8,9,12,13));
+----- HASH/KEY分区重建,分区数只能减小不能增加,增加可以通过ADD PARTITION方法
+ALTER TABLE users REORGANIZE PARTITION COALESCE PARTITION 2;
+
+新增分区
+----- 新增LIST分区
+ALTER TABLE category ADD PARTITION (PARTITION p4 VALUES IN (16,17,18,19)
+ DATA DIRECTORY = '/data8/data'
+ INDEX DIRECTORY = '/data9/idx');
+----- 新增HASH/KEY分区,将分区总数扩展到8个
+ALTER TABLE users ADD PARTITION PARTITIONS 8;
+
+
+
+[ 给已有的表加上分区 ]
+[sql] view plain copy
+
+ alter table results partition by RANGE (month(ttime))
+ (PARTITION p0 VALUES LESS THAN (1),
+ PARTITION p1 VALUES LESS THAN (2) , PARTITION p2 VALUES LESS THAN (3) ,
+ PARTITION p3 VALUES LESS THAN (4) , PARTITION p4 VALUES LESS THAN (5) ,
+ PARTITION p5 VALUES LESS THAN (6) , PARTITION p6 VALUES LESS THAN (7) ,
+ PARTITION p7 VALUES LESS THAN (8) , PARTITION p8 VALUES LESS THAN (9) ,
+ PARTITION p9 VALUES LESS THAN (10) , PARTITION p10 VALUES LESS THAN (11),
+ PARTITION p11 VALUES LESS THAN (12),
+ PARTITION P12 VALUES LESS THAN (13) );
+
+
+
+默认分区限制分区字段必须是主键(PRIMARY KEY)的一部分,为了去除此
+限制:
+[方法1] 使用ID
+[sql] view plain copy
+
+ mysql> ALTER TABLE np_pk
+ -> PARTITION BY HASH( TO_DAYS(added) )
+ -> PARTITIONS 4;
+
+ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function
+
+However, this statement using the id column for the partitioning column is valid, as shown here:
+
+[sql] view plain copy
+
+ mysql> ALTER TABLE np_pk
+ -> PARTITION BY HASH(id)
+ -> PARTITIONS 4;
+
+Query OK, 0 rows affected (0.11 sec)
+Records: 0 Duplicates: 0 Warnings: 0
+
+[方法2] 将原有PK去掉生成新PK
+[sql] view plain copy
+
+ mysql> alter table results drop PRIMARY KEY;
+
+Query OK, 5374850 rows affected (7 min 4.05 sec)
+Records: 5374850 Duplicates: 0 Warnings: 0
+
+[sql] view plain copy
+
+ mysql> alter table results add PRIMARY KEY(id, ttime);
+
+Query OK, 5374850 rows affected (6 min 14.86 sec)
+
+Records: 5374850 Duplicates: 0 Warnings: 0
+
+http://www.cnblogs.com/chenmh/p/5623474.html
+
+http://www.cnblogs.com/martinzhang/p/3467232.html
+
+http://www.simlinux.com/archives/133.html
+
+http://blog.csdn.net/tjcyjd/article/details/11194489
+
+http://blog.51yip.com/mysql/1013.html
+
+https://segmentfault.com/a/1190000006812384
+
+查看EXPLAIN命令
+
+EXPLAIN PARTITIONS
+
+查看各个分区当前的数据量
+
+SELECT partition_name part, partition_method method, partition_expression expr, partition_description descr, table_rows
+ FROM INFORMATION_SCHEMA.partitions WHERE TABLE_SCHEMA = schema() AND TABLE_NAME='foobar_partition';
+
+http://phpzf.blog.51cto.com/3011675/793775
+
+L 180/100A 胸围 104 腰围 102 肩阔 45 后中长 70 袖长 44
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+https://www.byvoid.com/zhs/blog/string-hash-compare
+
+
+
+
+
+{% highlight text %}
+{% endhighlight %}
diff --git a/_drafts/2015-07-18-mysql-performance-schema_init.md b/_drafts/2015-07-18-mysql-performance-schema_init.md
new file mode 100644
index 0000000..c64d9eb
--- /dev/null
+++ b/_drafts/2015-07-18-mysql-performance-schema_init.md
@@ -0,0 +1,455 @@
+---
+title: MySQL Performance Schema
+layout: post
+comments: true
+language: chinese
+category: [mysql,database]
+---
+
+这一存储引擎应该是在 MySQL 5.5 引入的,主要用于收集数据库服务器性能参数,提供等待事件的详细信息,包括锁、互斥变量、文件信息等;其中的表是只读的,用户不能创建或者修改。
+
+在此简单介绍下。
+
+
+
+## 简介
+
+这一存储引擎应该是在 MySQL 5.5 引入的,主要用于收集数据库服务器性能参数,提供等待事件的详细信息,包括锁、互斥变量、文件信息等;其中的表是只读的,用户不能创建或者修改。
+
+另外,需要注意的是,开启 PS 会有一定的性能损耗,不同的版本号,不同的分支可能略有不同,如果比较关注性能,应该以实测为准。
+
+在该库中,数据表分为如下的几类:
+
+1. setup table:设置表,配置监控选项;
+2. current events table:记录当前那些 thread 正在发生什么事情;
+1. history table 发生的各种事件的历史记录表;
+1. summary table 对各种事件的统计表;
+1. 杂项表,乱七八糟表。
+
+
+
+很多资料也可以参考官方网站的介绍 [MySQL Performance Schema](http://dev.mysql.com/doc/refman/en/performance-schema.html) 。
+
+### 开启
+
+如果使用 Performance Schema,需要在编译时进行配置,可以通过如下命令查看是否支持。
+
+{% highlight text %}
+$ mysqld --verbose --help | grep performance_schema
+ ... ...
+ --performance-schema
+ Enable the performance schema.
+ --performance-schema-accounts-size=#
+ Maximum number of instrumented user@host accounts. Use 0
+ to disable, -1 for automated sizing.
+ ... ...
+{% endhighlight %}
+
+在 5.5.6 之前,默认是关闭的,之后默认是打开的,在配置文件中通过如下方式进行设置,注意,这个参数是静态参数,只能在 my.cnf 设置,不能动态修改。
+
+{% highlight text %}
+[mysqld]
+performance_schema=ON
+{% endhighlight %}
+
+设置完重启后,可以查看相应变量,判断是否已经启动;若返回值为 ON,则说明性能数据库正常开启状态;如果是 OFF 可以查看日志为什么启动失败。
+
+{% highlight text %}
+mysql> SHOW VARIABLES LIKE 'performance_schema';
+{% endhighlight %}
+
+Performance Schema 通过存储引擎插件实现,也就意味着可以通过如下命令查看。
+
+{% highlight text %}
+mysql> SELECT * FROM INFORMATION_SCHEMA.ENGINES WHERE ENGINE='PERFORMANCE_SCHEMA'\G
+*************************** 1. row ***************************
+ ENGINE: PERFORMANCE_SCHEMA
+ SUPPORT: YES
+ COMMENT: Performance Schema
+TRANSACTIONS: NO
+ XA: NO
+ SAVEPOINTS: NO
+
+mysql> SHOW ENGINES\G
+... ...
+ Engine: PERFORMANCE_SCHEMA
+ Support: YES
+ Comment: Performance Schema
+Transactions: NO
+ XA: NO
+ Savepoints: NO
+... ...
+{% endhighlight %}
+
+对应的表存储在 performance_schema 库中,所包含的表可以通过如下两种方式查看,对应的表会随着新监控的添加慢慢增长。
+
+{% highlight text %}
+mysql> SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'performance_schema';
+mysql> SHOW TABLES FROM performance_schema;
+{% endhighlight %}
+
+默认不是所有的 instrument 和 consumer 都会被 enable ,这也就意味着一开始不会收集所有的事件,可以通过执行如下 SQL 。
+
+{% highlight text %}
+mysql> UPDATE setup_instruments SET ENABLED = 'YES', TIMED = 'YES';
+Query OK, 338 rows affected (0.12 sec)
+mysql> UPDATE setup_consumers SET ENABLED = 'YES';
+Query OK, 8 rows affected (0.00 sec)
+{% endhighlight %}
+
+如果想查看某个时刻的等待事件,可以查询 events_waits_current 表,它记录了每个 thread 最近的监控信息。
+
+
+
+
+
+
+
+
+
+
+## setup table
+
+通过如下方式可以查看所有的 setup 表。
+
+{% highlight text %}
+mysql> SELECT table_name FROM information_schema.tables WHERE \
+ table_schema = 'performance_schema' AND table_name LIKE 'setup%';
++-------------------+
+| table_name |
++-------------------+
+| setup_actors | 配置用户纬度的监控,默认监控所有用户
+| setup_consumers | 消费者类型,即收集的事件写入到哪些统计表中
+| setup_instruments | 该数据库的表名以及是否开启监控
+| setup_objects | 监控对象
+| setup_timers | 监控选项已经采样频率的时间间隔
++-------------------+
+5 rows in set (0.00 sec)
+{% endhighlight %}
+
+### setup_actors
+
+配置用户纬度的监控,默认监控所有用户。
+
+{% highlight text %}
+mysql> SELECT * FROM performance_schema.setup_actors;
++------+------+------+---------+---------+
+| HOST | USER | ROLE | ENABLED | HISTORY |
++------+------+------+---------+---------+
+| % | % | % | YES | YES |
++------+------+------+---------+---------+
+1 row in set (0.00 sec)
+{% endhighlight %}
+
+
+### setup_consumers
+
+对于 setup_consumers 表,需要注意的是,如果要采集数据,需要确保其 ENABLED 列为 YES 才会收集,可以直接使用 UPDATE SQL 进行更新,更新完后立即生效。
+
+{% highlight text %}
+mysql> SELECT * FROM performance_schema.setup_consumers ;
++----------------------------------+---------+
+| NAME | ENABLED |
++----------------------------------+---------+
+| events_stages_current | NO |
+| events_stages_history | NO |
+| events_stages_history_long | NO |
+| events_statements_current | YES |
+| events_statements_history | YES |
+| events_statements_history_long | NO |
+| events_transactions_current | NO |
+| events_transactions_history | NO |
+| events_transactions_history_long | NO |
+| events_waits_current | NO |
+| events_waits_history | NO |
+| events_waits_history_long | NO |
+| global_instrumentation | YES |
+| thread_instrumentation | YES |
+| statements_digest | YES |
++----------------------------------+---------+
+15 rows in set (0.00 sec)
+{% endhighlight %}
+
+
+
+
+上述通过 UPDATE 配置后,服务器重启又会变回默认值,要永久生效需要在配置文件里添加。
+
+{% highlight text %}
+[mysqld]
+performance_schema_consumer_events_waits_current = ON
+performance_schema_consumer_events_stages_current = ON
+performance_schema_consumer_events_statements_current = ON
+performance_schema_consumer_events_waits_history = ON
+performance_schema_consumer_events_stages_history = ON
+performance_schema_consumer_events_statements_history = ON
+{% endhighlight %}
+
+也即在这些表的前面加上performance_schema_consumer_xxx。
+
+其中 history 和 history_long 保存的是 current 表的历史记录条数,长度可以通过变量设置。
+
+{% highlight text %}
+mysql> SHOW VARIABLES LIKE 'performance_schema%history%size';
++----------------------------------------------------------+-------+
+| Variable_name | Value |
++----------------------------------------------------------+-------+
+| performance_schema_events_stages_history_long_size | 1000 |
+| performance_schema_events_stages_history_size | 10 |
+| performance_schema_events_statements_history_long_size | 1000 |
+| performance_schema_events_statements_history_size | 10 |
+| performance_schema_events_transactions_history_long_size | 1000 |
+| performance_schema_events_transactions_history_size | 10 |
+| performance_schema_events_waits_history_long_size | 1000 |
+| performance_schema_events_waits_history_size | 10 |
++----------------------------------------------------------+-------+
+8 rows in set (0.00 sec)
+{% endhighlight %}
+
+### setup_instruments
+
+配置具体的 instrument,主要包含如下的几大类:idle、stage/xxx、statement/xxx、wait/xxx、memory/xxx、transaction 。
+
+{% highlight text %}
+mysql> SELECT name, count(1) FROM performance_schema.setup_instruments GROUP BY left(name,5);
++-------------------------------------------+----------+
+| name | count(1) |
++-------------------------------------------+----------+
+| idle | 1 |
+| memory/performance_schema/mutex_instances | 375 |
+| stage/sql/After create | 129 |
+| statement/sql/select | 193 |
+| transaction | 1 |
+| wait/synch/mutex/sql/TC_LOG_MMAP::LOCK_tc | 314 |
++-------------------------------------------+----------+
+6 rows in set (0.01 sec)
+{% endhighlight %}
+
+idle 表示 socket 空闲的时间;stage 类表示语句的每个执行阶段的统计;statement 类统计语句维度的信息;wait 类统计各种等待事件,比如 IO、mutux、spin_lock、condition 等。
+
+
+### setup_objects
+
+默认对 MySQL、performance_schema 和 information_schema 中的表都不监控,而其它 DB 的所有表都监控。
+
+
+
+{% highlight text %}
+mysql> SELECT * FROM performance_schema.setup_objects;
++-------------+--------------------+-------------+---------+-------+
+| OBJECT_TYPE | OBJECT_SCHEMA | OBJECT_NAME | ENABLED | TIMED |
++-------------+--------------------+-------------+---------+-------+
+| EVENT | mysql | % | NO | NO |
+| EVENT | performance_schema | % | NO | NO |
+| EVENT | information_schema | % | NO | NO |
+| EVENT | % | % | YES | YES |
+| FUNCTION | mysql | % | NO | NO |
+| FUNCTION | performance_schema | % | NO | NO |
+| FUNCTION | information_schema | % | NO | NO |
+| FUNCTION | % | % | YES | YES |
+| PROCEDURE | mysql | % | NO | NO |
+| PROCEDURE | performance_schema | % | NO | NO |
+| PROCEDURE | information_schema | % | NO | NO |
+| PROCEDURE | % | % | YES | YES |
+| TABLE | mysql | % | NO | NO |
+| TABLE | performance_schema | % | NO | NO |
+| TABLE | information_schema | % | NO | NO |
+| TABLE | % | % | YES | YES |
+| TRIGGER | mysql | % | NO | NO |
+| TRIGGER | performance_schema | % | NO | NO |
+| TRIGGER | information_schema | % | NO | NO |
+| TRIGGER | % | % | YES | YES |
++-------------+--------------------+-------------+---------+-------+
+20 rows in set (0.00 sec)
+{% endhighlight %}
+
+
+### setup_timers
+
+配置每种类型指令的统计时间单位。MICROSECOND表示统计单位是微妙,CYCLE表示统计单位是时钟周期,时间度量与CPU的主频有关,NANOSECOND表示统计单位是纳秒。但无论采用哪种度量单位,最终统计表中统计的时间都会装换到皮秒。(1秒=1000000000000皮秒)
+
+{% highlight text %}
+mysql> SELECT * FROM performance_schema.setup_timers;
++-------------+-------------+
+| NAME | TIMER_NAME |
++-------------+-------------+
+| idle | MICROSECOND |
+| wait | CYCLE |
+| stage | NANOSECOND |
+| statement | NANOSECOND |
+| transaction | NANOSECOND |
++-------------+-------------+
+5 rows in set (0.00 sec)
+{% endhighlight %}
+
+## instance
+
+### cond_instances
+
+条件等待对象实例,表中记录了系统中使用的条件变量的对象,OBJECT_INSTANCE_BEGIN 为对象的内存地址。
+
+### file_instances
+
+记录了系统中打开了文件的对象,包括 ibdata、redo、binlog、用户的表文件等,open_count 显示当前文件打开的数目,如果没有打开过,不会出现在表中。
+
+{% highlight text %}
+mysql> SELECT * FROM performance_schema.file_instances;
++-------------------------------------------+--------------------------------------+------------+
+| FILE_NAME | EVENT_NAME | OPEN_COUNT |
++-------------------------------------------+--------------------------------------+------------+
+| /opt/mysql-5.7/share/english/errmsg.sys | wait/io/file/sql/ERRMSG | 0 |
+| /opt/mysql-5.7/share/charsets/Index.xml | wait/io/file/mysys/charset | 0 |
+| /tmp/mysql-master/ibdata1 | wait/io/file/innodb/innodb_data_file | 3 |
+| /tmp/mysql-master/ib_logfile0 | wait/io/file/innodb/innodb_log_file | 2 |
+| /tmp/mysql-master/ib_logfile1 | wait/io/file/innodb/innodb_log_file | 2 |
+| /tmp/mysql-master/mysql/engine_cost.ibd | wait/io/file/innodb/innodb_data_file | 3 |
++-------------------------------------------+--------------------------------------+------------+
+269 rows in set (0.01 sec)
+{% endhighlight %}
+
+
+
+### socket_instances
+
+记录活跃会话对象实例,表中记录了 thread_id、socket_id、ip 和 port,其它表可以通过 thread_id 与 socket_instance 进行关联,获取 IP-PORT 信息,能够与应用对接起来。
+
+{% highlight text %}
+mysql> select * from socket_instances;
++----------------------------------------+-----------+-----------+------------------+-------+--------+
+| EVENT_NAME | THREAD_ID | SOCKET_ID | IP | PORT | STATE |
++----------------------------------------+-----------+-----------+------------------+-------+--------+
+| wait/io/socket/sql/server_tcpip_socket | 1 | 33 | :: | 3307 | ACTIVE |
+| wait/io/socket/sql/server_unix_socket | 1 | 34 | | 0 | ACTIVE |
+| wait/io/socket/sql/client_connection | 31 | 41 | ::ffff:127.0.0.1 | 54834 | ACTIVE |
+| wait/io/socket/sql/client_connection | 32 | 45 | | 0 | ACTIVE |
++----------------------------------------+-----------+-----------+------------------+-------+--------+
+4 rows in set (0.00 sec)
+{% endhighlight %}
+
+event_name 主要包含 3 类:
+
+* wait/io/socket/sql/server_unix_socket,服务端 unix 监听 socket;
+* wait/io/socket/sql/server_tcpip_socket,服务端 tcp 监听 socket;
+* wait/io/socket/sql/client_connection,客户端 socket 。
+
+
+
+
+
+
+
+
+
+
+
+
+## 其它
+
+### threads
+
+监视服务端的当前运行的线程。
+
+
+
+
+{% highlight text %}
+----- 哪个SQL执行最多
+mysql> SELECT schema_name,digest_text,count_star,sum_rows_sent,sum_rows_examined
+ FROM events_statements_summary_by_digest ORDER BY count_star desc\G
+
+----- 哪个SQL平均响应时间最多
+mysql> SELECT schema_name,digest_text,count_star,avg_timer_wait,sum_rows_sent,sum_rows_examined
+ FROM events_statements_summary_by_digest ORDER BY AVG_TIMER_WAIT desc LIMIT 1\G
+
+{% endhighlight %}
+
+
+## 源码解析
+
+该引擎的源码保存在 storage/perfschema 目录下,
+
+{% highlight text %}
+mysqld_main()
+ |-pre_initialize_performance_schema() 最先调用的函数
+ |-initialize_performance_schema()
+ |-initialize_performance_schema_acl()
+ |-check_performance_schema()
+{% endhighlight %}
+
+
+
+
+
+
+{% highlight text %}
+{% endhighlight %}
diff --git a/_drafts/2015-07-27-mysql-transaction_init.md b/_drafts/2015-07-27-mysql-transaction_init.md
new file mode 100644
index 0000000..7b4ded8
--- /dev/null
+++ b/_drafts/2015-07-27-mysql-transaction_init.md
@@ -0,0 +1,902 @@
+---
+title: MySQL 事务处理
+layout: post
+comments: true
+language: chinese
+category: [mysql,database]
+keywords: mysql,事务,源码,XA,TM,RM
+description: 事务处理是当前大多数数据库所需的,而不同的数据库实现方式又略有区别,在此介绍一下 MySQL 的实现方式,包括了 XA、。
+---
+
+事务处理是当前大多数数据库所需的,而不同的数据库实现方式又略有区别,在此介绍一下 MySQL 的实现方式,包括了 XA、。
+
+
+
+
+## 简介
+
+首先,介绍一下一些常见的概念,也就是 XA 。
+
+XA (transaction accordant) 是由 X/Open 组织提出的分布式事务的规范,主要定义了全局事务管理器 (Transaction Manager, TM) 和局部资源管理器 (Resource Manager, RM)之间的接口。
+
+MySQL XA 分为了内部 XA 与外部 XA 两类,内部 XA 用于同一实例下跨多个引擎的事务,由 binlog 作为协调者;外部 XA 用于跨多 MySQL 实例的分布式事务,需要应用层介入作为协调者,包括崩溃时的悬挂事务、全局提交还是回滚。
+
+通常会将事务的提交分成了两个阶段,也就是两阶段提交 (tow phase commit, 2PC):
+
+1. Prepare 阶段 第一阶段,事务管理器向所有涉及到的数据库服务器发出 "准备提交"(prepare) 请求,数据库收到请求后执行数据修改和日志记录等处理,处理完成后只是把事务的状态改成 "可以提交",然后把结果返回给事务管理器。
+
+2. Commit 阶段 第二阶段,在第一阶段中所有数据库都提交成功,那么事务管理器向数据库服务器发出 "确认提交" 请求,数据库服务器把事务的 "可以提交" 状态改为 "提交完成" 状态,然后返回应答。
+
+如果在第一阶段内有任何一个数据库的操作发生了错误,或者事务管理器收不到某个数据库的回应,则认为事务失败,回撤所有数据库的事务;数据库服务器收不到第二阶段的确认提交请求,也会把 "可以提交" 的事务回滚。
+
+
+## 外部 XA 事务
+
+也就是 MySQL 的跨库事务,或者称之为分布式事务,注意必须使用 serializable 隔离级别,而且只有 Innodb 支持。
+
+{% highlight text %}
+XA {START | BEGIN} xid // 开始一个事务,并将事务置于ACTIVE状态,此后执行的SQL语句都将置于该事务中
+XA END xid // 将事务置于IDLE状态,表示事务内SQL操作完成
+XA COMMIT xid [ONE PHASE] // 事务最终提交,完成持久化,事务完成;ONE PHASE将prepare和commit一步完成
+XA PREPARE xid // 实现事务提交的准备工作,事务状态置于PREPARED状态
+XA ROLLBACK xid // 事务回滚并终止
+{% endhighlight %}
+
+对于一个应用大致的操作示例如下。
+
+{% highlight text %}
+xa begin 'xa-trans'; // A1,两个数据库分别创建两个分布式事务
+xa start 'xa-trans'; // A2
+
+... ... // 向两个库表中分别写入数据
+
+xa end 'xa-trans'; // A1,SQL操作完成
+xa end 'xa-trans'; // A2
+
+xa prepare 'xa-trans'; // A1,事务准备提交
+xa prepare 'xa-trans'; // A2
+
+xa commit 'xa-trans'; // A1,事务最终提交
+xa commit 'xa-trans'; // A2
+{% endhighlight %}
+
+
+## 内部 XA 事务
+
+MySQL 为了兼容其它非事物引擎的复制,在 server 层引入了 binlog, 它可以记录所有引擎中的修改操作,因而可以对所有的引擎使用复制功能。
+
+另外,由于 MySQL 采用插件式存储架构,导致开启 binlog 后,事务提交实际是采用 2PC 来保证 binlog 和 redolog 的一致性,以及事务提交和回滚等;这也就是内部 XA 事务。
+
+MySQL 通过 WAL (Write-Ahead Logging) 方式,来保证数据库事务的一致性 (consistent) 和持久性 (durability);这是一种实现事务日志的标准方法,具体而言就是修改记录前,一定要先写日志;事务提交过程中,一定要保证日志先落盘,才能算事务提交完成。
+
+在提交过程中,主要做了如下的几件事情:
+
+1. 是清理 undo 段信息,对于 innodb 存储引擎的更新操作来说,undo 段需要 purge,这里的 purge 主要职能是,真正删除物理记录。在执行delete或update操作时,实际旧记录没有真正删除,只是在记录上打了一个标记,而是在事务提交后,purge线程真正删除,释放物理页空间。因此,提交过程中会将undo信息加入purge列表,供purge线程处理。
+
+2. 然后是释放锁资源,mysql通过锁互斥机制保证不同事务不同时操作一条记录,事务执行后才会真正释放所有锁资源,并唤醒等待其锁资源的其他事务;
+
+3. 再就是刷redo日志,前面我提到了,mysql实现事务一致性和持久性的机制。通过redo日志落盘操作,保证了即使修改的数据页没有即使更新到磁盘,只要日志是完成了,就能保证数据库的完整性和一致性;
+
+4. 最后就是清理保存点列表,每个语句实际都会有一个savepoint(保存点),保存点作用是为了可以回滚到事务的任何一个语句执行前的状态,由于事务都已经提交了,所以保存点列表可以被清理了。
+
+其中相关的包括了 MySQL 的锁机制、purge 原理、redo 日志、undo 段等内容,其实都是数据库的核心。
+
+
+1. prepare,第一阶段 binlog 不作任何操作。InnoDB prepare,包括的操作有 A) 持有 prepare_commit_mutex;B) 并且 write/sync redo log;C) 将回滚段设置为 Prepared 状态。
+
+2. commit,第二阶段 包括 write/sync Binlog。InnoDB commit,在写入 COMMIT 标记后释放 prepare_commit_mutex。
+
+其中,以 binlog 的写入与否作为事务提交成功与否的标志,innodb commit 标志并不是事务成功与否的标志。此时,事务崩溃恢复过程如下:
+
+1. 崩溃恢复时,扫描最后一个 Binlog 文件,提取其中的 xid。
+
+2. InnoDB 维持了状态为 prepare 的事务链表,将这些事务的 xid 和 binlog 中记录的 xid 做比较,如果在 binlog 中存在,则提交,否则回滚事务;从而可以让 InnoDB 和 binlog 中的事务状态保持一致。
+
+在 prepare 阶段或者在 write/sync binlog 阶段崩溃,也会回滚;在写入 innodb commit 标志时崩溃,则恢复时,会重新对 commit 标志进行写入。
+
+
+## 组提交
+
+MySQL 5.1 中,如果同时打开 sync_binlog=1、innodb_flush_log_at_trx_commit=1,TPS 将会下降到几十;这是因为 InnoDB 提交事务时,不仅需要将 REDO 刷盘,还需要将 Binlog 刷盘,每个事务都需要 2 次 sync 操作。而机械磁盘的 IOPS 也就为几百,所以导致 InnoDB 的性能极差。
+
+
+
+为了保证主从库的一致性,必须要保证 Binlog 和 InnoDB 的一致性,即如果一个事务写入了 Binlog,InnoDB 中就必须提交该事务;相反,如果一个事务没有写入 Binlog,InnoDB 就不能提交该事务。
+
+为此,在 MySQL 中的做法是:A) InnoDB 执行 Prepare,将 Redo 日志写磁盘;B) 将 Binlog 写磁盘;C) InnoDB 执行 Commit,将事务标记为提交。这样,可以保证 Binlog 和 InnoDB 的一致性,可以分三种情况考虑:
+
+1. 在 InnoDB Prepare 阶段 Crash。MySQL 在启动时做崩溃恢复,InnoDB 会回滚这些事务,同时由于事务也没有写到 binlog,InnoDB 和 Binlog 一致。
+
+2. 在 Binlog 写磁盘阶段 Crash。MySQL 在启动做崩溃恢复时,会扫描未成功提交的事务,和当时未成功关闭的 binlog 文件,如果事务已经 Prepare 了,并且也已经在 Binlog 中了,InnoDB 会提交该事务;相反,如果事务已经在 Prepare 中了,但是不在 Binlog 中,InnoDB 会回滚该事务。结果就是 InnoDB 和 Binlog 一致。
+
+3. 在 InnoDB 执行 Commit 阶段 Crash。和 2 类似,由于事务已经成功 Prepare,并且存在 Binlog 文件中,InnoDB 在崩溃恢复时,仍然会提交该事务,确保 Binlog 和 InnoDB 一致。
+
+也就是说,统一按照 binlog 的为准。在实现时,将 mysql_bin_log 作为 2 阶段提交的协调者 (详见 ha_commit_trans)。
+
+
+于是,对 binlog 采用组提交 (注意,prepare 阶段没有变),将该过程分为三个阶段。
+
+1. flush stage,将各个线程的 binlog 从 cache 写到文件中;
+
+2. sync stage 对binlog做fsync操作(如果需���的话;最重要的就是这一步,对多个线程的binlog合并写入磁盘);
+
+3. commit stage 为各个线程做引擎层的事务commit(这里不用写redo log,在prepare阶段已写)。每个stage同时只有一个线程在操作。(分成三个阶段,每个阶段的任务分配给一个专门的线程,这是典型的并发优化)
+
+这种实现的优势在于三个阶段可以并发执行,从而提升效率。
+
+
+
+
+
+# 事务操作
+
+MySQL 提供了多种方式来开启一个事务,最简单的就是以一条 BEGIN 语句开始,也可以以 START TRANSACTION 开启事务,你还可以选择开启一个只读事务还是读写事务。所有显式开启事务的行为都会隐式的将上一条事务提交掉。
+
+所有显示开启事务的入口函数均为 trans_begin(),如下列出了几种常用的事务开启方式。
+
+### BEGIN
+
+与该命令等效的命令还有 "BEGIN WORK" 及 "START TRANSACTION",下面仍以 BEGIN 为例。
+
+当以 BEGIN 开启一个事务时,首先会去检查是否有活跃的事务还未提交,如果没有提交,则调用 ha_commit_trans() 提交之前的事务,并释放之前事务持有的 MDL() 锁。
+
+执行 BEGIN 命令并不会真的去引擎层开启一个事务,仅仅是为当前线程设定标记,表示为显式开启的事务。
+
+
+### START TRANSACTION READ ONLY
+
+使用该选项开启一个只读事务,当以这种形式开启事务时,会为当前线程的 thd->tx_read_only 设置为 true。当服务端接受到任何数据更改的 SQL 时,都会直接拒绝请求,会返回如下的错误码,不会进入引擎层。
+
+下面的文件会在编译的时候生成。
+
+{% highlight cpp %}
+// build/include/mysqld_ername.h
+{ "ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION", 1792, "Cannot execute statement in a READ ONLY transaction." },
+
+// build/include/mysqld_error.h
+#define ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION 1792
+
+// build/include/sql_state.h
+{ ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION,"25006", "" },
+{% endhighlight %}
+
+这个选项可以强约束一个事务为只读的,而只读事务在引擎层可以走优化过的逻辑,相比读写事务的开销更小,例如不用分配事务id、不用分配回滚段、不用维护到全局事务链表中。
+
+这种方式从 5.6 版本开始引入,在该版本中将全局事务链表拆成了两个链表:一个用于维护只读事务,一个用于维护读写事务。这样我们在构建一个一致性视图时,只需要遍历读写事务链表即可。
+
+不过在 5.6 版本中,InnoDB 并不具备事务从只读模式自动转换成读写事务的能力,因此需要用户显式的使用以下两种方式来开启只读事务:A) 执行 START TRANSACTION READ ONLY;B) 将变量 tx_read_only 设置为 true 。
+
+5.7 版本引入了模式自动转换的功能,但该语法依然保留了。
+
+
+
+### START TRANSACTION READ WRITE
+
+用于开启读写事务,这也是默认的事务模式;如果当前实例的 read_only 打开了且当前连接不是超级账户,则显示开启读写事务会报错。
+
+
+
+
+
+
+
+
+
+
+
+
+## 源码详解
+
+其中与事务提交相关的涉及到两个重要的参数,innodb_flush_log_at_trx_commit 和 sync_binlog,不同的模式区别在于,写文件调用 write 和落盘 fsync 调用的频率不同,所导致的后果是 mysqld 或 os crash 后,不严格的设置可能会丢失事务的更新。
+
+双一模式是最严格的模式 (也就是上述参数均为一),这种设置情况下,单机在任何情况下不会丢失事务更新;不过同时也会带来极大的性能影响。
+
+所有显示开启事务的入口函数均为 trans_begin() 。
+
+### 初始化
+
+在实例启动时,会选择使用哪种 XA 方式,默认的就是 BINLOG 和 ENGINE 做 XA,如果 BINLOG 禁止了,则只用引擎做 XA。
+
+{% highlight cpp %}
+static int init_server_components() // 初始化tc_log变量
+{
+ ... ...
+ if (total_ha_2pc > 1 || (1 == total_ha_2pc && opt_bin_log))
+ {
+ if (opt_bin_log)
+ tc_log= &mysql_bin_log; // 打开binlog时,使用mysql_bin_log做2PC协调者
+ else
+ tc_log= &tc_log_mmap; // 没有打开binlog,且存在超过两个支持2PC的引擎时
+ }
+ else
+ tc_log= &tc_log_dummy; // 不存在支持2PC的引擎,或只有1个支持2PC的引擎且没有打开binlog时
+ ... ...
+}
+{% endhighlight %}
+
+其中 total_ha_2pc 代表了支持 2PC 引擎的数目,在 ha_initialize_handlerton() 初始化各个存储接口时,如果发现引擎的 prepare() 函数被定义,就会 total_ha_2pc++。
+
+需要注意的是 binlog 模块也算是支持 2PC 的引擎,也就是只要又一个存储引擎执行 preprare() 正常,则 total_ha_2pc 值就大于 1 。
+
+* TC_LOG_DUMMY 直接调用引擎接口做 PREPARE/COMMIT/ROLLBACK,基本不做任何协调。
+
+* MYSQL_BIN_LOG 实际上 binlog 接口不做 prepare,只做 commit,也就所有写入 binlog 文件的事务,在崩溃恢复时,都应该能够提交。
+
+* TC_LOG_MMAP 使用 mmap() 方式映射到内存中,实现事务日志的策略。
+
+上述类都是从 TC_LOG 这个纯虚类继承而来的,该类以及相关处理方法是日志子系统中为两阶段事务 (2PC) 提供的,并且在 binlog 中继承了该结构。
+
+其中比较重要的方法包括了如下的几种:open() 打开日志文件、close() 关闭日志文件、log_xid() 记录 2PC 事务日志 id、unlog() 删除 2PC 事务日志 id。
+
+
+### TC_LOG_MMAP
+
+在此重点介绍 TC_LOG_MMAP,其中一个重要的结构体就是 st_page,使得日志按照页的方式进行组织和操作。
+
+{% highlight cpp %}
+class TC_LOG_MMAP: public TC_LOG {
+ typedef struct st_page {
+ struct st_page *next; // 指向下一个日志页,链接成一个FIFO队列
+ my_xid *start, *end; // 指向页的第一个和最后一个位置
+ my_xid *ptr; // 下一个要记录xid的位置
+ int size, free; // 日志页可以存储xid的数目以及空闲的存储空间数目
+ int waiters; // 当前页处于条件等待的操作数目
+ PAGE_STATE state; // 当前页的状态,主要包括PS_POOL、PS_ERROR、PS_DIRTY三种状态
+ mysql_mutex_t lock; // 用于控制日志页的并发操作
+ mysql_cond_t cond; // 等待sync()
+ } PAGE;
+
+ char logname[FN_REFLEN]; // mmap的日志文件名
+ File fd; // 文件描述符
+ my_off_t file_length; // 日志文件的大小
+ uint npages; // 日志文件的页数,其中日志文件是按照页进行组织
+ uint inited; // 一个流程状态参数,用于表明当前操作所处的状态
+ uchar *data; // 用于存储xid数据内容
+ struct st_page *pages; // 用于组织数据页
+ struct st_page *syncing, *active, *pool; // 日志页分为syncing、active、pool三种状态存在
+ struct st_page **pool_last_ptr; // 该指针指向pool队列中的最后一个元素
+ mysql_mutex_t LOCK_active, LOCK_pool, LOCK_sync;// 三个锁结构,用于并发控制访问日志
+ mysql_cond_t COND_pool, COND_active; // 该条件变量分别用于并发控制中,唤醒pool队列和active页访问
+};
+#define TC_LOG_PAGE_SIZE 8192
+#define TC_LOG_MIN_SIZE (3*TC_LOG_PAGE_SIZE)
+{% endhighlight %}
+
+其中日志页按照队列的方式进行组织,其中 TC_LOG 日志文件默认每页大小为 8K,文件最小为 3 页,可以根据配置文件和启动参数指定日志文件大小。
+
+
+
+### 执行流程
+
+无论对于 DML (update, delete, insert)、TCL (commit, rollback) 语句,MySQL 提供了公共接口 mysql_execute_command(),基本流程如下:
+
+{% highlight cpp %}
+mysql_execute_command(THD *thd) {
+ switch (lex->sql_command) {
+ case SQLCOM_INSERT:
+ mysql_insert();
+ break;
+ case SQLCOM_UPDATE:
+ mysql_update();
+ break;
+ case SQLCOM_BEGIN:
+ trans_begin()
+ break;
+ case SQLCOM_COMMIT:
+ trans_commit();
+ break;
+ ......
+ }
+
+ if (thd->is_error() || ...) // 语句执行错误
+ trans_rollback_stmt(thd);
+ else
+ trans_commit_stmt(thd);
+}
+{% endhighlight %}
+
+可以看到执行任何语句最后,都会执行 trans_rollback/commit_stmt(),这两个调用分别是语句级提交和语句级回滚。
+
+语句级提交,对于非自动模式提交情况下,主要作两件事情,一是释放autoinc锁,这个锁主要用来处理多个事务互斥地获取自增列值,因此,无论最后该语句提交或是回滚,该资源都是需要而且可以立马放掉的。二是标识语句在事务中位置,方便语句级回滚。
+
+{% highlight text %}
+CREATE TABLE a (
+ id int(10) unsigned NOT NULL,
+ weight int(10) unsigned default NULL,
+ PRIMARY KEY (id)
+) ENGINE=InnoDB;
+CREATE TABLE b (
+ id int(10) unsigned NOT NULL,
+ weight int(10) unsigned default NULL,
+ PRIMARY KEY (id)
+) ENGINE=TokuDB;
+
+INSERT INTO a VALUES(1, 70),(2, 80);
+INSERT INTO b VALUES(1, 70),(2, 80);
+
+SET autocommit = 0;
+SHOW VARIABLES LIKE 'log_bin';
+SET SESSION debug = 'd:t:i:o,/tmp/mysqld.trace';
+
+######################################################################
+### 最常见的,也即打开binlog,且只使用了InnoDB引擎
+### 此时tc_log使用的是mysql_bin_log
+log-bin = 1, a (innodb)
+
+mysql> START TRANSACTION | BEGIN;
+mysql_execute_command() SQLCOM_BEGIN
+ |-trans_begin()
+ |-trans_check_state()
+
+mysql> INSERT INTO a VALUES(9, 100);
+Query OK, 1 row affected (0.01 sec)
+mysql_execute_command() SQLCOM_INSERT
+ |-insert_precheck()
+ | |-check_access()
+ | |-check_grant()
+ |-mysql_insert()
+
+mysql> COMMIT;
+Query OK, 0 rows affected (0.00 sec)
+mysql_execute_command() SQLCOM_COMMIT
+ |-trans_commit()
+ |-trans_check_state()
+ |-ha_commit_trans() stmt(false)
+ | |-commit_owned_gtids()
+ | |-tc_log->prepare() 1. MYSQL_BINLOG::prepare()
+ | | |-ha_prepare_low()
+ | | |-##### for()中循环中遍厉所有接口,在此也就是binlog+InnoDB
+ | | |-ht->prepare() 调用存储引擎handlerton->prepare()
+ | | | ### 注意,实际调用如下的两个函数
+ | | |-binlog_prepare()
+ | | |-innobase_xa_prepare()
+ | | |-check_trx_exists()
+ | | |-innobase_trx_init()
+ | |
+ | |-tc_log->commit() 2. MYSQL_BIN_LOG::commit()
+ | |-ordered_commit() 在此涉及到了binlog的组提交,详细《日志相关》
+ | |-change_stage() 2.1 转为FLUSH_STAGE,写入binlog缓存
+ | |-process_flush_stage_queue()
+ | |-flush_cache_to_file()
+ | |
+ | |-change_stage() 2.2 转为SYNC_STAGE,调用fsync()刷入磁盘
+ | |-sync_binlog_file() 调用fsync()写入磁盘
+ | |
+ | |-change_stage() 2.3 转为COMMIT_STAGE,提交阶段
+ | |-process_commit_stage_queue()
+ | | |-ha_commit_low()
+ | | |-##### for()中循环中遍厉所有接口,在此也就是binlog+InnoDB
+ | | |-ht->commit() 调用存储引擎handlerton->commit()
+ | | | ### 注意,实际调用如下的两个函数
+ | | |-binlog_commit()
+ | | |-innobase_commit()
+ | |-process_after_commit_stage_queue()
+ | |-finish_commit()
+ |
+ |-trans_track_end_trx()
+
+
+
+
+
+
+
+
+
+
+
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+
+log-bin = 0, a (innodb), b (tokudb)
+
+mysql> START TRANSACTION | BEGIN;
+Query OK, 0 rows affected (0.00 sec)
+mysql_execute_command() SQLCOM_BEGIN
+ |-trans_begin() 开启事务
+ | |-trans_check_state()
+ | |-ha_commit_trans() 如果有未提交的事务,先提交掉
+ |
+ |-ha_commit_trans()
+ | |-ha_commit_trans()
+ | |-tc_log->commit() 实际调用的是TC_LOG_MMAP::commit()
+ |-MDL_context::release_transactional_locks()
+ |- ... ... 将thd.server_status设置为SERVER_STATUS_IN_TRANS
+
+mysql> UPDATE a SET weight = 30 WHERE id = 1;
+Query OK, 1 row affected (0.00 sec)
+Rows matched: 1 Changed: 1 Warnings: 0
+mysql_execute_command() SQLCOM_UPDATE
+ |-update_precheck()
+ |-mysql_update()
+
+mysql> UPDATE b SET weight = 30 WHERE id = 1;
+mysql> COMMIT;
+mysql_execute_command() SQLCOM_COMMIT
+ |-trans_commit()
+
+
+
+######################################################################
+log-bin = 0, a (innodb)
+
+mysql> START TRANSACTION | BEGIN;
+Query OK, 1 row affected (0.01 sec)
+mysql> INSERT INTO a VALUES(4, 100);
+Query OK, 1 row affected (0.01 sec)
+mysql_execute_command() SQLCOM_COMMIT
+ |-insert_precheck()
+ |-mysql_insert()
+ | |-open_tables_for_query()
+ | | |-open_tables()
+ | | |-open_and_process_table()
+ | | |-open_table()
+ | | |-my_hash_first_from_hash_value()
+ | | |-innobase_trx_init()
+ | | |-column_bitmaps_signal()
+ | |
+ | |-write_record()
+ | |-handler::ha_write_row()
+ | |-ha_innobase::write_row()
+ | |-row_ins
+ | |-row_ins_index_entry_step
+ | |-row_ins_clust_index_entry
+ | |-row_ins_clust_index_entry_low
+ |
+ |-trans_commit_stmt()
+ |-ha_commit_trans()
+ |-ha_commit_low()
+ |-innobase_commit()
+
+mysql> COMMIT;
+Query OK, 1 row affected (0.01 sec)
+mysql_execute_command() SQLCOM_COMMIT
+ |-trans_commit
+ |-commit_owned_gtids(...)
+ |-ha_commit_trans
+ |-ha_commit_low
+ |-innobase_commit
+ |-innobase_trx_init
+ | |-THD::get_trans_pos
+ |-innobase_commit_low()
+ |-trx_commit_complete_for_mysql()
+
+
+
+
+
+///////////////
+log-bin = 1
+sync_binlog = 1
+
+
+------------------------------------------
+mysql> start transaction;
+Query OK, 0 rows affected (0.00 sec)
+ trans_begin()
+ trans_commit_stmt
+
+mysql> insert into x4 values (10);
+Query OK, 1 row affected (1 min 1.69 sec)
+ trans_commit_stmt
+ ha_commit_trans
+ binlog_prepare
+ innobase_xa_prepare
+ ha_commit_one_phase
+ binlog_commit(看起来什么也没做,仅仅做了cache_mngr->trx_cache.set_prev_position(MY_OFF_T_UNDEF);)
+ cache_mngr->trx_cache.set_prev_position(MY_OFF_T_UNDEF);
+ innobase_commit(同样仅仅是做一些标记)
+
+mysql> commit ;
+ trans_commit
+ ha_commit_trans
+ binlog_prepare
+ innodb_xa_prepare
+ trx_prepare_for_mysql
+ mysql_mutex_lock(&prepare_commit_mutex);
+ MYSQL_BIN_LOG::log_xid
+ ha_commit_one_phase
+ binlog_commit(似乎木有做写binlog,莫非已经被group commit了。。。= =! log_xid..)
+ innobase_commit
+ innobase_commit_low
+ mysql_mutex_unlock(&prepare_commit_mutex);
+ trx_commit_complete_for_mysql(trx);
+ trans_commit_stmt
+
+
+
+
+
+
+
+
+
+
+
+
+trans_commit_stmt()
+ |-ha_commit_trans()
+ |-tc_log->commit() 未开启binlog和gtid,则调用TC_LOG_MMAP::commit()
+ | 开启binlog后,则调用MYSQL_BIN_LOG::commit()
+ |
+ |-check_trx_exists() 获取innodb层对应的事务结构
+ |-innobase_trx_init()
+ |
+ | #### 通过if判断需要事务提交
+ |-innobase_commit_low()
+ | |-trx_commit_for_mysql()
+ | |-trx_commit()
+ | |-trx_commit_low()
+ | |-trx_write_serialisation_history() 更新binlog位点,设置undo状态
+ | | |-trx_undo_update_cleanup() 供purge线程处理,清理回滚页
+ | |-trx_commit_in_memory() 释放锁资源,清理保存点列表,清理回滚段
+ | |-trx_flush_log_if_needed() 刷日志
+ |
+ |-trx_deregister_from_2pc()
+ |-trx_commit_complete_for_mysql() 确定事务对应的redo日志是否落盘
+ | |-trx_flush_log_if_needed()
+ | |-trx_flush_log_if_needed_low()
+ | |-log_write_up_to() 根据参数决定刷磁盘的时机
+ |
+ | #### 单个语句,且非自动提交
+ |-lock_unlock_table_autoinc() 释放自增列占用的autoinc锁资源
+ |-trx_mark_sql_stat_end() 标识sql语句在事务中的位置,方便语句级回滚
+
+
+MYSQL_BIN_LOG::prepare()
+ |-ha_prepare_low()
+ |-ht->prepare() 实际调用如下的三个函数
+ |-binlog_prepare() engine
+ |-innobase_xa_prepare() engine
+ |-trx_prepare_for_mysql() mysql
+ |-trx_start_if_not_started_xa()
+ | |-trx_start_if_not_started_xa_low()
+ | |-trx_start_low()
+ |-trx_prepare()
+ |-trx_undo_set_state_at_prepare() 设置undo段的标记为TRX_UNDO_PREPARED
+ |-trx->state=TRX_STATE_PREPARED 设置事务状态为TRX_STATE_PREPARED
+ |-trx_flush_log_if_needed() 将产生的redolog刷入磁盘
+
+
+
+
+
+
+xa start 'pp'
+ trans_xa_start
+ trans_begin (隐含的commit任何当前的事务并释放锁)
+ trans_check :(/* Conditions under which the transaction state must not change. */)
+ (likely(value)、unlikely(value))
+ ha_commit_trans()
+ thd->mdl_context.release_transactional_locks()
+ trans_commit_stmt
+
+mysql> insert into x4 values(5);
+ trans_commit_stmt
+ ha_commit_trans
+ binlog_prepare:空函数
+ innobase_xa_prepare
+ 对于xa事务,仅仅标记为ended
+ ……
+ ha_commit_one_phase
+ binlog_commit(该函数在每执行一次statement后)
+ cache_mngr->trx_cache.set_prev_position(MY_OFF_T_UNDEF)
+ //binlog_commit_flush_trx_cache:将binlog cache中的内容写到文件中。
+ //binlog_flush_cache
+ innobase_commit (对这条语句而言,仅仅标记该语句结束了,而不做commit)
+
+mysql> xa end 'pp';
+ trans_commit_stmt
+ after_commit
+
+
+mysql> xa prepare 'pp';
+ trans_xa_prepare
+ ha_prepare
+ binlog_prepare
+ innobase_xa_prepare
+ ……
+ trx_prepare_for_mysql
+ trx_prepare_off_kernel-------根据my.cnf的选项决定是否刷新log到磁盘----做一次group commit
+
+ 当不为XA_PREPARE语句时。。。
+ if (thd_sql_command(thd) != SQLCOM_XA_PREPARE
+ pthread_mutex_lock(&prepare_commit_mutex);
+ trans_commit_stmt
+ after_commit
+
+mysql> xa commit 'pp';
+ trans_xa_commit
+92 /*
+693 * Acquire metadata lock which will ensure that COMMIT is blocked
+694 * by active FLUSH TABLES WITH READ LOCK (and vice versa COMMIT in
+695 * progress blocks FTWRL).We allow FLUSHer to COMMIT; we assume FLUSHer knows what it does.
+ */
+ 加锁……
+ ha_commit_one_phase(thd, 1)
+ binlog_commit
+ binlog_commit_flush_trx_cache
+ binlog_flush_cache
+ innobase_commit
+ innobase_commit_low
+ trx_commit_for_mysql
+ trx_commit_off_kernel(看起来似乎是真的要提交了。。。。)
+
+
+ if (trx->declared_to_be_inside_innodb) { //不知道为什么没有持有prepare_commit_mutex
+
+
+ trx_commit_complete_for_mysql
+
+
+log_write_up_to:(log0log.c)
+实现group commit,这里仅考虑flushto_disk为真的情况,即需要向磁盘中fsync log
+loop:
+ 当ut_dulint_cmp(log_sys->flushed_to_disk_lsn, lsn) >= 0
+ 即lsn小于或等于全局变量log_sys中的记录的上次刷新的lsn时,直接返回
+
+ log_sys->n_pending_writes > 0
+ if (flush_to_disk
+ && ut_dulint_cmp(log_sys->current_flush_lsn, lsn)
+ >= 0) {
+ /* The write + flush will write enough: wait for it to
+ complete */
+
+
+ goto do_waits;
+ }
+
+
+
+{% endhighlight %}
+
+
+## 常见配置项
+
+
+##### flush_log_at_trx_commit
+
+在 trx_commit_complete_for_mysql() 函数中,会根据 flush_log_at_trx_commit 参数,确定 redo 日志落盘方式。
+
+##### binlog_max_flush_queue_time(0) 单位为us
+
+设置从 flush 队列中取事务的超时时间,防止并发事务过高,导致某些事务的 RT 上升,详细的实现参考 MYSQL_BIN_LOG::process_flush_stage_queue()。
+
+##### binlog_order_commits(ON)
+事务和binlog是否以相同顺序提交
+
+
+不以顺序提交时可以稍微提升点性能,但并不是特别明显。
+
+innodb_support_xa(ON) 是否启用 innodb 的 XA
+它会导致一次额外的磁盘flush(prepare阶段flush redo log). 但是我们必须启用,而不能关闭它。因为关闭会导致binlog写入的顺序和实际的事务提交顺序不一致,会导致崩溃恢复和slave复制时发生数据错误。如果启用了log-bin参数,并且不止一个线程对数据库进行修改,那么就必须启用innodb_support_xa参数。
+
+
+
+
+
+
+## 事务ID
+
+在 InnoDB 中一直维护了一个不断递增的整数,存储在 trx_sys->max_trx_id 中,该事务 ID 可以看做一个事务的唯一标识;每次开启一个新的读写事务时,都将该 ID 分配给事务,同时递增全局计数。
+
+{% highlight text %}
+trx_start_low() # innobase/trx/trx0trx.cc
+ |-trx_sys_get_new_trx_id() # innobase/include/trx0sys.ic
+{% endhighlight %}
+
+在 MySQL5.6 及之前的版本中,总是为事务分配 ID,而实际上只有做过数据更改的读写事务,才需要去根据事务 ID 判断可见性。因此在 MySQL5.7 版本中,只有读写事务才会分配事务 ID,只读事务默认为 0 。
+
+
+
+对于全局最大事务 ID,每做 TRX_SYS_TRX_ID_WRITE_MARGIN (256) 次修改后,就持久化一次到 ibdata 的事务页 (TRX_SYS_PAGE_NO) 中。
+
+
+
+
+
+
+
+
+{% highlight cpp %}
+/** The transaction system central memory data structure. */
+struct trx_sys_t{
+
+ ib_mutex_t mutex; /*!< mutex protecting most fields in
+ this structure except when noted
+ otherwise */
+ ulint n_prepared_trx; /*!< Number of transactions currently
+ in the XA PREPARED state */
+ ulint n_prepared_recovered_trx; /*!< Number of transactions
+ currently in XA PREPARED state that are
+ also recovered. Such transactions cannot
+ be added during runtime. They can only
+ occur after recovery if mysqld crashed
+ while there were XA PREPARED
+ transactions. We disable query cache
+ if such transactions exist. */
+ trx_id_t max_trx_id; /*!< The smallest number not yet
+ assigned as a transaction id or
+ transaction number */
+#ifdef UNIV_DEBUG
+ trx_id_t rw_max_trx_id; /*!< Max trx id of read-write transactions
+ which exist or existed */
+#endif
+ trx_list_t rw_trx_list; /*!< List of active and committed in
+ memory read-write transactions, sorted
+ on trx id, biggest first. Recovered
+ transactions are always on this list. */
+ trx_list_t ro_trx_list; /*!< List of active and committed in
+ memory read-only transactions, sorted
+ on trx id, biggest first. NOTE:
+ The order for read-only transactions
+ is not necessary. We should exploit
+ this and increase concurrency during
+ add/remove. */
+ trx_list_t mysql_trx_list; /*!< List of transactions created
+ for MySQL. All transactions on
+ ro_trx_list are on mysql_trx_list. The
+ rw_trx_list can contain system
+ transactions and recovered transactions
+ that will not be in the mysql_trx_list.
+ There can be active non-locking
+ auto-commit read only transactions that
+ are on this list but not on ro_trx_list.
+ mysql_trx_list may additionally contain
+ transactions that have not yet been
+ started in InnoDB. */
+ trx_rseg_t* const rseg_array[TRX_SYS_N_RSEGS];
+ /*!< Pointer array to rollback
+ segments; NULL if slot not in use;
+ created and destroyed in
+ single-threaded mode; not protected
+ by any mutex, because it is read-only
+ during multi-threaded operation */
+ ulint rseg_history_len;/*!< Length of the TRX_RSEG_HISTORY
+ list (update undo logs for committed
+ transactions), protected by
+ rseg->mutex */
+ UT_LIST_BASE_NODE_T(read_view_t) view_list;
+ /*!< List of read views sorted
+ on trx no, biggest first */
+};
+
+{% endhighlight %}
+
+
+
+事务子系统维护了三个不同的链表,用来管理事务对象。
+
+trx_sys->mysql_trx_list
+包含了所有用户线程的事务对象,即使是未开启的事务对象,只要还没被回收到trx_pool中,都被放在该链表上。当session断开时,事务对象从链表上摘取,并被回收到trx_pool中,以待重用。
+
+trx_sys->rw_trx_list
+读写事务链表,当开启一个读写事务,或者事务模式转换成读写模式时,会将当前事务加入到读写事务链表中,链表上的事务是按照trx_t::id有序的;在事务提交阶段将其从读写事务链表上移除。
+
+trx_sys->serialisation_list
+序列化事务链表,在事务提交阶段,需要先将事务的undo状态设置为完成,在这之前,获得一个全局序列号trx->no,从trx_sys->max_trx_id中分配,并将当前事务加入到该链表中。随后更新undo等一系列操作后,因此进入提交阶段的事务并不是trx->id有序的,而是根据trx->no排序。当完成undo更新等操作后,再将事务对象同时从serialisation_list和rw_trx_list上移除。
+
+这里需要说明下trx_t::no,这是个不太好理清的概念,从代码逻辑来看,在创建readview时,会用到序列化链表,链表的第一个元素具有最小的trx_t::no,会赋值给ReadView::m_low_limit_no。purge线程据此创建的readview,只有小于该值的undo,才可以被purge掉。
+
+总的来说,mysql_trx_list包含了rw_trx_list上的事务对象,rw_trx_list包含了serialisation_list上的事务对象。
+
+事务ID集合有两个:
+
+trx_sys->rw_trx_ids
+记录了当前活跃的读写事务ID集合,主要用于构建ReadView时快速拷贝一个快照
+
+trx_sys->rw_trx_set
+这是的映射集合,根据trx_id排序,用于通过trx_id快速获得对应的事务对象。一个主要的用途就是用于隐式锁转换,需要为记录中的事务id所对应的事务对象创建记录锁,通过该集合可以快速获得事务对象
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{% highlight cpp %}
+struct trx_t{
+ ulint magic_n;
+ ib_mutex_t mutex;
+ trx_id_t id; /*!< transaction id */
+};
+{% endhighlight %}
+
+
+
+http://blog.chinaunix.net/uid-26896862-id-3527584.html
+http://mysql.taobao.org/monthly/2015/12/01/
+http://mysql.taobao.org/monthly/2015/11/04/
+
+
+
+{% highlight text %}
+{% endhighlight %}
diff --git a/_drafts/2015-07-31-mysql-replication-mmm_init.md b/_drafts/2015-07-31-mysql-replication-mmm_init.md
new file mode 100644
index 0000000..830841d
--- /dev/null
+++ b/_drafts/2015-07-31-mysql-replication-mmm_init.md
@@ -0,0 +1,624 @@
+---
+title: MySQL 高可用 MMM
+layout: post
+comments: true
+language: chinese
+category: [mysql,database]
+keywords: mysql,高可用,mmm
+description: MySQL-MMM (Master-Master Replication Manager for MySQL) 是 Google 的一个开源项目,用来监控 MySQL 双主+多读架构,并在失败时完成自动切换,不过该方案不适用对数据一致性要求很高的业务。其原理是将真实数据库节点的 IP 映射为虚拟 IP 地址,包括了一个专用于写的 IP,多个用于读的 IP 。接下来,看看详细的实现原理。
+---
+
+MySQL-MMM (Master-Master Replication Manager for MySQL) 是 Google 的一个开源项目,用来监控 MySQL 双主+多读架构,并在失败时完成自动切换,不过该方案不适用对数据一致性要求很高的业务。
+
+原理是将真实节点的 IP 映射为虚拟 IP 地址,包括了一个专用于写的 IP,多个用于读的 IP 。
+
+接下来,看看详细的实现原理。
+
+
+
+## 简介
+
+MMM 适用于如下的两种场景,一种是双主,另外一种是双主+多个只读备库。
+
+![mmm]({{ site.url }}/images/databases/mysql/mmm-sample-setup-1.png "mmm"){: .pull-center }
+
+![mmm]({{ site.url }}/images/databases/mysql/mmm-sample-setup-2.png "mmm"){: .pull-center }
+
+简单来说,MMM 主要是为了保证系统的高可用,而非数据一致性,如下是其优缺点:
+
+##### 优点:
+
+1. 自动完成切换,包括了主主Failover切换;
+2. 多个Slave读的负载均衡;
+3. 自动的VIP切换,通过ARP广播发送,这也就意味着需要在同一个网段中,否则需要用到虚拟路由技术;
+4. 支持抖动检测,防止服务器状态频繁切换;
+
+##### 缺点:
+
+1. 无法完全保证数据的一致性,备库落后时执行切换会导致数据丢失;
+2. 无论何时可以保证只有一个可写(通过readonly设置),但切换时原链接没有直接断开,可能会导致不一致性;
+3. monitor单点问题;
+
+MySQL-MMM中 有三种比较核心的概念:节点状态(States)、角色(Roles)、模式(Modes),如下简单介绍下:
+
+### 相关脚本
+
+MySQL-MMM的主要功能通过以下三个脚本来实现:
+mmm_mond:监控进程,监控所有服务器,决定节点角色。
+mmm_agentd:代理进程,运行在每台MySQL服务器上,为监控节点提供远程执行;TODODO: 是否支持双向通讯。
+mmm_control:为mmm_mond提供管理命令,常见的有状态检查,控制节点上下线;
+
+
+### 节点状态
+
+也就是MySQL服务相关的状态,实际上可以通过如下状态判断是否需要发送报警,总共有六种,如下:
+1. ONLINE 正常运行状态,只有该状态才会有角色信息,切换之后角色将移除;
+2. ADMIN_OFFLINE 人工设置为离线状态,一般为手动运维操作,如更换内存;
+3. HARD_OFFLINE 离线状态,一般为ping主机失败或者mysql连接失败;
+4. AWAITING_RECOVERY 等待恢复,需要手动处理,一般是由于抖动(Flapping)或者没有设置自动恢复(TODODO:配置项是那个???)
+5. REPLICATION_DELAY 复制延时过大,也就是rep_backlog检测失败,实际通过SHOW SLAVE STATUS检测Seconds_Behind_Master状态值;
+6. REPLICATION_FAIL 复制线程没有运行,也就是rep_threads检测失败,实际通过SHOW SLAVE STATUS检测Slave_IO_Running或者Slave_SQL_Running是否为No状态。
+
+其中状态转换在Monitor的主处理逻辑的_check_host_states()函数中检测。
+
+FIXME: 状态切换比较复杂,远超文档中的介绍,后面再具体梳理吧
+当主机处在REPLICATION_DELAY或REPLICATION_FAIL状态,一旦恢复,将切换到ONLINE状态。除非抖动不稳定。
+主机在HARD_OFFLINE状态,如果所有的问题都解决了,那么将会切换到AWAITING_RECOVERY状态。如果它故障时间小于60s,并且它没有重启或者auto_set_online>0,
+那么将会被自动切换到ONLINE状态,除非抖动不稳定。
+活动的主服务器复制延时或复制失败将不被视为一个问题。因此活动的主服务器状态不会被置于REPLICATION_DELAY或REPLICATION_FAIL。
+在节点被切换到ONLINE状态的60s内,如果复制延时或复制失败将会被忽略(默认时间为master-connect-retry值)。
+如果rep_backlog和rep_threads都检测失败,将会切换到REPLICATION_FAIL状态。
+A host that was in state REPLICATION_DELAY or REPLICATION_FAIL will be switched back to ONLINE if everything is OK again, unless it is flapping (see Flapping).
+A host that was in state HARD_OFFLINE will be switched to AWAITING_RECOVERY if everything is OK again. If its downtime was shorter than 60 seconds and it wasn't rebooted or auto_set_online is > 0 it will be switched back to ONLINE automatically, unless it is flapping (see Flapping again).
+Replication backlog or failure on the active master isn't considered to be a problem, so the active master will never be in state REPLICATION_DELAY or REPLICATION_FAIL.
+Replication backlog or failure will be ignored on hosts whos peers got ONLINE less than 60 seconds ago (That's the default value of master-connect-retry).
+If both checks rep_backlog and rep_threads fail, the state will change to REPLICATION_FAIL.
+
+### 检测方式
+
+mmm_mond对每个主机检测4项决定是否OK
+1. ping 主机是否存活
+2. mysql mysqld 进程是否存活
+3. rep_threads 复制线程是否运行,通过SHOW SLAVE STATUS检测Slave_IO_Running或者Slave_SQL_Running是否为No状态。
+4. rep_backlog 延时少、复制积压的日志很少,通过SHOW SLAVE STATUS检测Seconds_Behind_Master状态值。
+
+
+3、角色
+
+exclusive角色:互斥角色只有一个ip,并且同一时间只能分配给一个主机。可以指定一个首选主机(preferred 存疑??),如果这个主机是ONLINE状态
+
+,那么角色将被赋予到这个主机。注意:不能移动被分配到首选主机的角色,因为他们将立刻再次被移动回到它。
+
+balanced角色:负载均衡角色可以有多个ip。没有一个主机可以比其他主机多出两个角色。
+TODODO: 难点,既保证可以快速回复,有需要尽量避免Flapping。
+
+## 抖动检测
+
+mmm_mond支持抖动检测,通常是在网络不稳定时,例如光纤老化;抖动检测主要是针对主机频繁在ONLINE和(HARD_OFFLINE|REPLICATION_FAIL|REPLICATION_DELAY)状态之间切换,
+
+
+每次切换到ONLINE状态(auto_set_online或者
+
+down的时间小于60s),将会导致角色的切换非常频繁。
+
+
+为了避免这种情况mmm_mond内建了flap检测,可以通过配置文件配置。
+
+
+如果一个主机在flap_duration时间内宕掉了flap_count次,则认为该主机处于flap状态,就不会自动被设置为ONLINE状态,此时主机将一直处于AWAITING_RECOVERY状态,直到手动设置为online (mmm_control set online host)。
+
+
+
+如果auto_set_online>0,处于flapping的主机在flap_duration时间后将自动设置为ONLINE状态
+
+TODODO: 业务高峰期,可能会导致频繁的复制延迟???加权限
+
+???5、模式
+
+active mode:Monitor将会自动的把角色从失败的主机上移除,并切换到其他主机上。
+
+manual mode:Moniter会自动把负载均衡的角色分配给对应主机,但是不会自动的把角色从失败的主机上移除。可以通过move_role来手工移除。
+
+wait mode:类似manual模式,但是当两个master都是online状态或者超过了wait_for_other_master的时间,将被切换为ACTIVE模式。
+
+passive mode:在此模式下,monitor不会改变角色,不更新状态文件和发送任何信息给mmm agents。在此模式下可以使用set_ip来改变roles,但是这些改变在monitor切换到
+
+ACTIVE或者MANUAL模式(set_active or set_manual)前都不会生效。在启动时检测到角色发生冲突将会进入被动模式。
+
+## 参考
+
+关于 MMM 的文档参考 [mysql-mmm.org](http://mysql-mmm.org/mysql-mmm.html) 中的介绍。
+
+http://www.cnblogs.com/chenmh/p/5563778.html
+
+What's wrong with MMM
+
+https://www.xaprb.com/blog/2011/05/04/whats-wrong-with-mmm/
+
+mmm_tools配置手册
+
+http://linuxguest.blog.51cto.com/195664/608338/
+
+MMM部署常见问题
+
+http://www.codexiu.cn/linux/blog/18484/
+
+ifconfig lo:0 127.0.1.1 netmask 255.255.255.0 up
+ifconfig lo:0 down
+
+ifconfig lo:1 127.0.1.1 netmask 255.255.255.0 up
+ifconfig lo:1 down
+
+
+用户权限设置,包括了mmm_agent(本地客户端操作,可以设置为本地登陆)、mmm_monitor(远程监控操作,可以只指定监控IP):
+
+GRANT REPLICATION CLIENT ON *.* TO 'mmm_monitor'@'127.0.1.%' IDENTIFIED BY 'monitor';
+GRANT SUPER, REPLICATION CLIENT, PROCESS ON *.* TO 'mmm_agent'@'127.0.1.%' IDENTIFIED BY 'agent';
+GRANT REPLICATION SLAVE ON *.* to 'mysync'@'127.0.1.%' IDENTIFIED BY 'kidding';
+
+MMM::Monitor::Monitor::init() 初始化
+ |-main() 主要的循环处理过程
+ |-MMM::Monitor::NetworkChecker::main() 启动一个线程监控ping_ips指定的IP列表
+ | ###WHILE###BEGIN
+ |-_process_check_results()
+ |-_check_host_states()
+ |-_process_commands()
+ |-_distribute_roles()
+ |-send_status_to_agents()
+ | ###WHILE###END
+
+
+mmm_agentd启动过程
+MMM::Agent::Agent::main()
+ |-create_listener()
+ | ###WHILE1###BEGIN
+ |-accept()
+ | ###WHILE2###BEGIN
+ |-handler_command()
+ | |-cmd_ping() PING命令,直接返回OK: Pinged!
+ | |-cmd_set_status() SET_STATUS命令,设置状态
+ | | |-MMM::Agent::Helper::set_active_master() 如果是备库,则设置主库,该函数中会执行CHANGE MASTER TO
+ | |-cmd_get_agent_status()
+ | |-cmd_get_system_status()
+ | |-cmd_clear_bad_roles()
+ |
+ | ###WHILE2###END
+ | ###WHILE1###END
+bin/agent/configure_ip 检查是否设置IP,如果没有设置,则设置并通过ARP通知其它服务器
+configure_ip
+MMM::Agent::Helpers::Network::send_arp()
+
+
+HA工具需求
+错误配置时(从其他机器复制配置文件过来)不会对现有环境造成影响;
+
+
+
+#### 配置文件
+
+DB服务器配置文件mmm_agent.conf、mmm_common.conf;监控服务器的配置文件mmm_mon.conf、mmm_common.conf;其中所有节点的mmm_common.conf文件都是相同的。
+
+#
+active_master_role writer ###积极的master角色的标示,所有的db服务器都需要开启read_only参数,对于writer服务器监控代理会自动将read_only属性关闭。
+
+
+ cluster_interface eth0 #####群集的网络接口
+ pid_path /var/run/mysql-mmm/mmm_agentd.pid ####pid路径
+ bin_path /usr/libexec/mysql-mmm/ #####可执行文件路径
+ replication_user repl #######复制用户
+ replication_password repl #######复制用户密码
+ agent_user mmm_agent #######代理用户,用于更改只读操作
+ agent_password mmm_agent #######代理用户密码
+
+
+ ##########master1的host名
+ ip 192.168.137.10 #####master1的ip
+ mode master ########角色属性,master代表是主
+ peer backup ########与master1对等的服务器的host名,也就是master2的服务器host名
+
+
+ ####和master的概念一样
+ ip 192.168.137.20
+ mode master
+ peer master
+
+
+ #####从库的host名,如果存在多个从库可以重复一样的配置
+ ip 192.168.137.30 ####从的ip
+ mode slave #####slave的角色属性代表当前host是从
+
+
+ ####writer角色配置
+ hosts master,backup ####能进行写操作的服务器的host名,如果不想切换写操作这里可以只配置master,这样也可以避免因为网络延时而进行write的切换,但是一旦master出现故障那么当前的MMM就没有writer了只有对外的read操作。
+ ips 192.168.137.100 #####对外提供的写操作的虚拟IP
+ mode exclusive #####exclusive代表只允许存在一个主,也就是只能提供一个写的IP
+
+
+ #####read角色配置
+ hosts backup,slave ######对外提供读操作的服务器的host名,当然这里也可以把master加进来
+ ips 192.168.137.120,192.168.137.130,192.168.137.140 ###对外提供读操作的虚拟ip,这两个ip和host不是一一对应的,并且ips也hosts的数目也可以不相同,如果这样配置的话其中一个hosts会分配两个ip
+ mode balanced ###balanced代表负载均衡
+
+
+include mmm_common.conf
+
+ ping_ips 192.168.137.10,192.168.137.20,192.168.137.30 # 被监控的db服务器的ip地址
+ ping_interval 1 # 默认是1秒,也就是ping主机的监控时间间隔,详见MMM::Monitor::NetworkChecker
+ auto_set_online 0 # 设置自动online的时间,默认是超过60s就将它设置为online,默认是60s,这里将其设为0就是立即online
+ ip 127.0.0.1
+ pid_path /var/run/mysql-mmm/mmm_mond.pid
+ bin_path /usr/libexec/mysql-mmm
+ status_path /var/lib/mysql-mmm/mmm_mond.status #####群集的状态文件,也就是执行mmm_control show操作的显示来源。
+
+ # The kill_host_bin does not exist by default, though the monitor will
+ # throw a warning about it missing. See the section 5.10 "Kill Host
+ # Functionality" in the PDF documentation.
+ #
+ # kill_host_bin /usr/libexec/mysql-mmm/monitor/kill_host
+ #
+
+
+### binlog_mode和隔离级别
+
+1. 查看当前数据库状态,包括了版本、当前数据库等。
+mysql> status
+
+2. 创建测试表
+mysql>CREATE TABLE foobar (
+ id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ book CHAR(10) DEFAULT NULL
+ ) ENGINE=InnoDB;
+Query OK, 0 rows affected (0.03 sec)
+
+3. 测试RC隔离级别,使用STATEMENT
+----- 改变事务级别为read committed
+mysql>set session transaction isolation level read committed;
+Query OK, 0 rows affected (0.00 sec)
+----- 改变二进制日志格式
+mysql>set binlog_format=STATEMENT;
+Query OK, 0 rows affected (0.00 sec)
+----- 插入数据测试
+mysql>insert into foobar(book) values('wuli');
+ERROR 1598 (HY000): Binary logging not possible. Message: Transaction level 'READ-COMMITTED' in InnoDB is not safe for binlog mode 'STATEMENT'
+
+4. 测试隔离级别RR,使用STATEMENT
+mysql>set session transaction isolation level REPEATABLE READ;
+Query OK, 0 rows affected (0.00 sec)
+mysql>insert into foobar(book) values('wuli');
+Query OK, 1 row affected (0.00 sec)
+
+5. 测试隔离级别RC,使用非STATEMENT格式
+mysql>set session transaction isolation level read committed;
+Query OK, 0 rows affected (0.00 sec)
+mysql>set session binlog_format=row; 改为行格式
+Query OK, 0 rows affected (0.00 sec)
+mysql>insert into foobar(book) values('wuli');
+Query OK, 1 row affected (0.00 sec)
+mysql>set session binlog_format=mixed; 改为混合格式
+Query OK, 0 rows affected (0.00 sec)
+mysql>insert into foobar(book) values('wuli');
+Query OK, 1 row affected (0.00 sec)
+
+那是因为read committed可能会导致不可重复读,也就是说可以读取到其它事务已经提交的数据,如果基于STATEMENT格式的话,会导致主从数据不一样,因为STATEMENT是基于SQL语句的复制模式。
+
+
+
+### 网页PPT
+https://mmonit.com/monit/
+https://vxhly.github.io/2016/09/03/reveal-js-cn-document/
+
+### master_pos_wait
+
+如果将MySQL设置为双主 M1(rw)+M2(r),正常switchover时的切换流程大致如下:
+1. 停止应用写M1,将M1设置为只读;
+2. 检查M2的slave status直到赶上M1;
+ 2.1 在M1上show master status;得到binlog位置P,因为已经设为只读,不会变化;
+ 2.2 循环检测M2上的执行位置,若未到P,则过几秒再查,循环直到从库追上;
+3. 将M1设置为可写。
+
+实际上,对于第2步,可以通过select master_pos_wait(file, pos[, timeout])函数完成,这里的file和pos对应于主库show master status得到的值,该函数会等待当前从库达到这个位置后返回,返回参数为这期间执行的事务数;超时时间可选,默认是无限等待(参数小于0)。
+
+返回值:-1)等待超时;NULL) 当前slave未启动或在等待期间被终止;0) 指定的值已经在之前达到。
+
+master_pos_wait的实现逻辑
+
+ 用户调用该函数后,根据传入参数调用pthread_cond_timedwait或pthread_cond_wait。 SQL_THREAD线程每次apply完一个事件后会触发更新relay info, 并通知上面等待的线程。因为可能有多个用户等待,因此用广播方式。
+
+ 关于事件个数的计算比较复杂,有兴趣的同学可以看这篇, 不过在本文讨论的这个问题上,正数返回值并不重要。
+
+用master_pos_wait来实现上面的步骤b,则可以简化为:
+b’) 在M上执行select master_pos_wait(file, pos),返回后判断一下返回值>=0 则认为主从同步完成。
+
+ 好处是
+1) 简化逻辑,不用在应用脚本判断
+2) 在追上的第一时间就能感知,否则可能多等若干秒
+
+#### syslog
+/etc/sysconfig/syslog
+/etc/syslog-ng/syslog-ng.conf
+
+syslog系统默认会存在两个守护进程klogd+syslog-ng(syslog-Next generation),前者用于记录内核生成的日志,后者是用于非内核信息。
+
+
+### syslog-ng
+http://cyr520.blog.51cto.com/714067/1245650
+http://ckl893.blog.51cto.com/8827818/1789967
+提供了Client、Relay、Server三种模式,对于Client来说,配置文件中包括了全局配置、消息源、过滤器、消息目的地和日志路径。
+
+# Global options.
+options { long_hostnames(off); sync(0); perm(0640); stats(3600); };
+
+# 'src' is our main source definition. you can add more sources driver
+# definitions to it, or define your own sources, i.e.:
+#source my_src { .... };
+source src {
+ # include internal syslog-ng messages
+ # note: the internal() soure is required!
+ internal();
+ # the default log socket for local logging:
+ unix-dgram("/dev/log");
+ # uncomment to process log messages from network:
+ #udp(ip("0.0.0.0") port(514));
+};
+
+source my_src {
+ udp(ip("127.0.0.1") port(514));
+};
+filter f_local2 { facility(local2); };
+destination d_haproxy { file("/var/log/haproxy.log", owner(elb), group(wheel), perm(0600)); };
+log { source(my_src); filter(f_local2); destination(d_haproxy); };
+
+
+#### 密码存储
+http://www.yunweipai.com/archives/4518.html
+http://www.jianshu.com/p/d13ff016ff88
+https://www.zhihu.com/question/20479856
+http://www.williamlong.info/archives/3224.html
+http://www.freebuf.com/articles/web/28527.html
+http://blog.csdn.net/shanliangliuxing/article/details/7365920
+
+
+### Perl DBD::mysql 安装
+
+安装perl-DBD-MySQL时,会出现错误提示依赖错误(error: Failed dependencies) libmysqlclient.so.18()(64bit) is needed by perl-DBD-MySQL-4.023-5.el7.x86_64 ,但是我们 mysql-community-libs 提供的是 libmysqlclient.so.20 动态库。当然,我们可以直接下载源码然后安装,不过也可以通过 mysql-community-libs-compat 安装,libmysqlclient.so.18
+
+Percona-toolkits环境安装
+1. 安装DBD::mysql模块
+源码从https://dev.mysql.com/downloads/dbi.html下载,
+
+perl Makefile.PL --mysql_config=/bin/mysql_config
+make
+make test
+make install
+
+#!/usr/bin/perl
+use DBI;
+$user="test";
+$passwd="test";
+$dbh="";
+$dbh = DBI->connect("dbi:mysql:database=test;host=192.1.1.168;port=3306",$user,$passwd) or die "can't connect to
+database ". DBI-errstr;
+$sth=$dbh->prepare("select * from infobright_loaddata_status");
+$sth->execute;
+while (@recs=$sth->fetchrow_array) {
+print $recs[0].":".$recs[1].":".$recs[2]."\n";
+}
+$dbh->disconnect;
+
+
+### Linux C 动态库依赖
+
+http://littlewhite.us/archives/301
+http://bbs.chinaunix.net/thread-4130723-1-1.html
+
+
+https://github.com/KredytyChwilowki/MySQLReplicaIntegrityCheck
+https://www.percona.com/blog/2016/03/17/mysql-replication-primer-with-pt-table-checksum-pt-table-sync-part-2/
+https://segmentfault.com/a/1190000004309169
+http://www.cnblogs.com/zhoujinyi/archive/2013/05/09/3067045.html
+
+保证数据一致性,常用的如数据校验、数据一致性备份。
+
+建议通过pt-table-checksum完成数据校验,然后通过pt-table-sync进行数据修复,原因是前者会提供很好的限流措施,可以尽量减小对现网的影响。
+
+### 使用参数
+
+直接从源码中截取。
+
+```
+if DSN has a t part, sync only that table:
+ if 1 DSN:
+ if --sync-to-master:
+ The DSN is a slave. Connect to its master and sync.
+ if more than 1 DSN:
+ The first DSN is the source. Sync each DSN in turn.
+else if --replicate:
+ if --sync-to-master:
+ The DSN is a slave. Connect to its master, find records
+ of differences, and fix.
+ else:
+ The DSN is the master. Find slaves and connect to each,
+ find records of differences, and fix.
+else:
+ if only 1 DSN and --sync-to-master:
+ The DSN is a slave. Connect to its master, find tables and
+ filter with --databases etc, and sync each table to the master.
+ else:
+ find tables, filtering with --databases etc, and sync each
+ DSN to the first.
+```
+
+简单来说,如果使用了--replicate或者--sync-to-master可以只使用一个DSN配置,否则就需要配置两个DSN值。
+
+#### 常用参数介绍如下
+
+* --sync-to-master
+将当前库视为备库,需要去同步主库数据,此时会修改--wait=60 --lock=1 --notransaction ;
+* --replicate
+与pt-table-checksum工具的对应参数相同,该工具会通过WHERE过滤检查数据,并与主库同步数据,如果没有通过--sync-to-master参数指定备库,那么就会尝试通过SHOW PROCESSLIST、SHOW SLAVE HOSTS查找备库;
+* --wait
+主库等待备库多长时间追上主库,如果超时则输出MASTER_POS_WAIT returned -1,此时可以尝试增加时间,默认会同时设置--lock=1 --notransaction;
+* --timeout-ok
+默认主库等待备库超时后会直接退出,通过该选项可以使程序继续执行,如果要保证一致性的比较最好退出!!!
+* --[no]check-triggers
+检测目标表上是否定义了trigger,如果定义默认会直接退出;
+* --execute
+如果有数据不一致可以直接执行,如果担心软件有问题,可以通过--print打印SQL而非执行;
+* --algorithms
+数据校验时采用的算法,目前支持chunk、nibble、groupby、stream四种算法;
+
+调试参数:
+* --print
+主要用于防止工具执行错误,此时不会真正执行表同步操作,只打印需要执行的SQL,用于手动执行;
+* --dry-run
+用于调试,会同时设置--verbose选项,此时工具并不会访问表,所以也无从知道哪些表出现了不一致,只用于显示会执行哪些操作;
+* --explain-hosts
+只打印链接各个数据库的参数信息,然后退出;
+
+
+### 数据校验算法
+
+数据校验算法目前包括了Chunk、Nibble、GroupBy、Stream四种算法,当然上述算法各有优缺点,在源码中通过插件实现,也可以新增新插件实现;在此,主要介绍前两种方式:
+* Chunk开始会将表分给为chunks,适用于区分度高的表;
+* Nibble与Chunk类似,不过分区是通过limit实现;
+
+### 执行流程
+
+
+在此,主要介绍指定--replicat参数时,该工具的详细执行流程:
+
+1. 从校验表中找到不一致的chunk,包括其边界,详见find_replication_differences()函数,然后一般是每行开始循环校验;
+2. 对主库使用FOR UPDATE对chunk加锁,从库使用LOCK IN SHARE MODE,正常来说在主库添加了悲观锁之后从库的数据就不会被修改;
+3. 上步同时会通过SELECT ... CRC32(CONCAT_WS('#', ...))生成校验码,这里实际上只使用主键,如果数据不一致后续统一获取各行的值;
+4. 如果上述有异常,则将数据保存到队列中,后续统一处理;
+5. 根据不同的类型,会生成REPLACE、INSERT、DELETE等语句;
+6. 通过lock_and_wait()等待主从同步完成,然后开始修复数据,每次会处理一行记录,并最后等待同步完成。
+
+
+### pt-table-sync
+
+perldoc /bin/pt-table-sync
+
+pt-table-sync --print --algorithms=chunk --charset=utf8 --replicate=zabbix.checksums h=localhost,u=checker,p=Opsmonitordb@2015 h=192.30.19.82,u=checker,p=Opsmonitordb@2015
+
+PTDEBUG=1
+
+PTDEBUG=1 pt-table-sync --print --execute --algorithms=chunk --charset=utf8 --replicate=zabbix.checksums h=localhost,u=checker,p=Opsmonitordb@2015 h=192.30.19.82,u=checker,p=Opsmonitordb@2015 >/tmp/1 2>&1
+
+### 登陆选项:
+可以通过DSN指定,也可以使用参数。
+--ask-pass
+--password
+--port
+--host
+--user
+--socket
+--slave-user
+--slave-password
+
+双向修复相关:
+--bidirectional
+--conflict-column
+--conflict-comparison
+--conflict-error
+--conflict-threshold
+--conflict-value
+
+* --[no]bin-log
+是否记录到binlog中,如果采用GTID,那么在备库修复会存在问题;
+
+
+### 过滤选项
+
+可以指定只检查哪些数据库、表、列、存储引擎等,可以使用Perl正则表达式,也可以指定忽略哪些。
+
+--ignore-columns
+--ignore-databases
+--ignore-engines
+--ignore-tables
+--ignore-tables-regex
+=item --columns
+=item --databases
+=item --tables
+=item --engines
+#### 安全检查
+
+用于检查是否可以进行同步,详细可以查看ok_to_sync()。
+
+--[no]check-child-tables
+--[no]check-master
+--[no]check-slave
+--[no]check-triggers
+--[no]foreign-key-checks
+
+
+=item --buffer-in-mysql
+=item --[no]buffer-to-client
+=item --charset
+=item --chunk-column
+=item --chunk-index
+=item --chunk-size
+=item --config
+=item --defaults-file
+=item --float-precision
+=item --function
+=item --[no]hex-blob
+=item --[no]index-hint
+=item --lock
+=item --lock-and-rename
+=item --pid
+=item --recursion-method
+=item --replace
+=item --set-vars
+=item --timeout-ok
+=item --[no]transaction
+=item --trim
+=item --[no]unique-checks
+=item --verbose
+=item --version
+=item --[no]version-check
+=item --wait
+=item --where
+=item --[no]zero-chunk
+
+GRANT ALL ON *.* TO 'checker'@'localhost';
+GRANT ALL ON *.* TO 'checker'@'192.30.19.83';
+
+main()
+ |-lock_and_rename() 如果使用了--lock-and-rename参数
+ |-sync_one_table() 只同步一个表,可通过--tables指定需要同步那些表
+ |-sync_via_replication() 指定了--replicate参数,重点查看
+ | | ###BEGIN###使用sync-to-master参数,也就是在备库执行
+ | |-find_replication_differences() 校验主库是否由不一致,如果有那么直接忽略对应表
+ | |-find_replication_differences() 查看备库中不一致的表
+ | |-filter_diffs() 过滤不需要同步的表
+ | |-lock_server() 只有在lock=3时才有效,会执行FLUSH TABLES WITH READ LOCK命令
+ | |-sync_a_table() 一个表一个表同步数据
+ | | |-get_server_time() 获取开始时间
+ | | |-ok_to_sync() 检测是否可以同步表:1)获取表结构;2)确认表在目的库存在;3)确认目的库没有定义trigger;4)如果需要执行,确认目的库没有外键子表
+ | | |-diff_where() 获取WHERE子句
+ | | |-get_change_dbh() 获取需要执行修改操作的链接信息
+ | | |-make_action_subs() 根据execute/print参数将设置回调actions函数
+ | | |-sync_table() 真正执行数据修复的函数
+ | | | |-get_best_plugin() 获取最适用的插件
+ | | | | |-can_sync() 遍历各个插件,获取第一个可以使用的插件
+ | | | | |-find_chunk_columns() Chunk算法
+ | | | |-prepare_to_sync()
+ | | | |-lock_and_wait() 处理表锁:1) COMMIT/UNLOCK TABLES;2) 开启事务或者锁表(lock=3);3) 通过MASTER_POS_WAIT()等待备库完成
+ | | | | |-wait_for_master() 等待主库
+ | | | |-###BEGIN###while 等待$plugin->done()完成
+ | | | |-lock_and_wait() 只有在一个chunk的开始和结束会执行
+ | | | |-execute() 在主库、备库分别执行
+ | | | |-compare_sets() 比较结果集如果正常则会打印Left and right have the same key,如果有异常则会保存在队列中,一般值含主键
+ | | | |-process_rows() 开始执行修复
+ | | | | |-make_$action() 执行修改,一般为make_REPLACE...
+ | | | | | |-make_row() 获取SQL
+ | | | | |-_take_action() 开始执行
+ | | | |-###END###while
+ | | |-get_server_time() 获取结束时间
+ | |-unlock_server()
+ | | ###END###sync-to-master
+ |-sync_all()
+
+{% highlight text %}
+{% endhighlight %}
diff --git a/_drafts/2015-08-01-wealth-financial-independence_init.md b/_drafts/2015-08-01-wealth-financial-independence_init.md
new file mode 100644
index 0000000..9ff13c2
--- /dev/null
+++ b/_drafts/2015-08-01-wealth-financial-independence_init.md
@@ -0,0 +1,228 @@
+---
+title: 如何实现财务自由?
+layout: post
+comments: true
+language: chinese
+category: [misc]
+keywords: 财务自由, financial independence
+description:
+---
+
+
+
+
+
+现金流量表 (赚钱和花钱)、利润表 (结余)、资产负债表 (合理借钱买资产) 。
+
+{% highlight text %}
+>>>>>>>>>>>>>>>>>>> 现金流表(月) <<<<<<<<<<<<<<<<<<<<<
+税后收入 20000.00
+房租 1300.00
+餐饮 1000.00
+购物 300.00
+杂项 0.00
+
+支出合计 2600.00
+结余 17400.00
+
+>>>>>>>>>>>>>>>>>> 资产负债表 <<<<<<<<<<<<<<<<<<<<
+=================== 资产 =====================
+1. 流动资产
+ A. 现金
+ 支付宝 23000.00
+ 工商银行 320000.00
+ 招商银行 155000.00
+ 京东金融 33000.00
+ 公积金 213000.00
+ 借出 130000.00 (梅元刚)
+ 贝米钱包 370000.00
+ 54000.00
+ B. 信用卡
+ C. 活期存款
+ D. 货币基金
+ E. 其它
+ ==> 合计 1244000.00
+
+2. 长期投资
+ A. 定期存款
+ B. 国债
+ C. 股票
+ D. 其它
+ ==> 合计
+
+3. 固定资产
+ A. 房产
+ B. 其它
+ ==> 合计
+
+4. 其它资产
+ A. 汽车
+ ==> 合计
+
+ =====> 总资产
+
+=================== 负债 =====================
+1. 短期负债
+ A. 信用卡
+ B. 消费贷款
+ C. 其它
+ ==> 合计
+2. 长期贷款
+ A. 房屋贷款
+ B. 汽车贷款
+ ==> 合计
+
+ =====> 总负债
+{% endhighlight %}
+
+
+
+
+## 定义
+
+首先要定义什么是财务自由,不同的人会有不同的定义,例如:非工资收入大于支出;
+
+这里的很多概念是从 罗伯特·清崎 先生的 《穷爸爸富爸爸》系列丛书中摘取的,鄙人
+
+
+
+
+
+
+一般收入来自于两部分:A) 工作收入、年终年金等;一部分是投资收入,进行金融投资或实业投资的投资所得。
+
+大部分人每月支出主要来自工作收入,没工作收入生活陷入困境。但如果投资收入够多,不用工作就可过好日子,随时可以退休或可以睡觉睡到然醒,就达到财务自由的境界了。当达到财务自由时,投资收入将成为个人收人的主要来源,不再为赚取生活费用而工作。
+
+理财的最终目标是追求财务自由,财务自由的定义是:不靠工作收入,靠理财收入就能过生活。要求每年的理财收入大于生活支出,这对没工作的退休人士特别重要,理财规划师也特别对即将退休的人在财务上有这样的要求。
+
+要达到财务自由是逐步完成的,中产阶级人士透过股票、债券、保险等投资获取现金流。
+
+比如你生活费用一个月要一万元,一年要12万元,这就是你的年生活支出。你现在有第一个100万,放在理财型保险,每年生存金加分红有4%收益率,一年有4万元,这是不够生活支出的。
+
+再存了第二个100万,放在高评等债券投资,每年利率6%,一年就有6万元,跟保险收益加起来10万元,还是达不到12万元的财务自由门坎。
+
+再努力储蓄50万,放在蓝筹股,每年发放现金股利8%,一年就有4万元,跟保险债券收益加起来14万元,就达到财务自由。
+
+
+
+| 投资工具 | 年收益率 | 数量 |
+| -------- | -----: | :----: |
+| 理财型保险 | $1 | 5 |
+| | $1 | 6 |
+| | $1 | 7 |
+
+
+
+
+股票
+债券
+保险
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+### 第一步,生活成本
+
+
+
+其实按我个人标准:当一个人的非工资收入≥总支出,他并 没有 达到财务自由。因为我已经实现这一步了,但我并没感到我在财务上有多自由。《富爸爸》系列作者罗伯特·清崎先生说过:“储蓄全都是输家的策略,你要尽快地实现财务自由。”我们更关心的是如何实现?我给出我早年的计划——理财七步走(走到第七步,就是实现了财务自由的目标。本人尚在第五、六步间)步骤一(财务自知阶段)列出你的每月支出(不能说出,要列出,写在纸上,或者在电脑上做个Excel表格。)以我本人婚前单身时候的情况为例(写现在的也行,不过那就长了去了):
+
+1、衣:一般三个月最多买一大件一小件,算下来摊到一个月100元就足够了;(这块单身男士比较省,有女生之后被迫骚包起来,这块就......(┬_┬)......都是泪)
+2、吃:三餐加零食(有抽烟的可以放一起,本人不抽)每日花费是40元,一个月就是1200元;
+3、住:房租和舍友一摊每月400元;
+4、行:当时还是摩托族,每月油费100元,顶天了。
+5、杂项:应酬,一般控制在500元以内,再加上其它如电话费、水费、电费……200吧。那么,总计我的生活费用就是2500元人民币一个月。(我这是大概啊,纯粹为了回答问题,照记忆简单的列列,当时可是做了几个月的明细,精确到角)各位真想对自个财务负责,还请按自个现状列个详单,精确到角。(别说没角,淘宝花钱,还是有的)步骤二(财务生存阶段)存好应急钱应急钱要纯多少?要存半年的生活费。(最少4个月。不能再少了)我当时一个月生活费2500元。半年的生活费就是2500 X 6 =15000元。这1.5万直接存银行定期,能不动绝对不动,能动也咬牙不动。
+
+(PS:这笔钱是我一工作就开始分批存的,所以网银上都是一长溜:1000的3个月定期、2000的一年定期、5000的六个月定期、1000的一年定期,......这样分批存,而且期限、数额不同,还有一个考虑是当时想实在要动这些钱,也可以不用全动,可以挑一些刚好到期限的小额的钱......当时,我真是好可爱~(≧▽≦)/~啦啦啦)为什么要存应急钱?
+给你安全感。当时在外漂泊,若一时失业、碰上大病、有其他事故都很正常(你不在外漂泊,碰上这些也很正常)。万一发生上述情况,我有半年生活费,我至少可以不必每天为了吃饭而发愁,一有事就卷铺盖回家。让我有6个月的时间给我翻身,这份安全感很重要,真的。我个人用这笔钱,让我从从容容的换了3次更好的工作(各位知友,你离开一个老单位,他卡你2个月工资,这种情况,熟悉吧?)。卡呗,就知道你会卡,卡我我也要走。(在此,感激一下曾经的一个老大——阿山经理,不但不卡,还多发了我半个月工资。)有人会问,现在银行定期的利息都不高,我存余额宝行不?这个,其实我自己也已经忘记“银行定期存款”这种东西了............这笔钱的特点有两个:1、是安全,绝对的安全。2、是尽量不动(定期)。
+余额宝,不是说他不安全,就怕你那天淘宝支付宝钱不够了....行吧,想存哪就存哪吧。反正你给我放在一个安全稳定的地方。当你存下了半年的应急钱,我们再开始向更高的阶段迈进。步骤三:下班要准时回家,(当然不是这个,不过真要回家了,待续......)
+________________________________________________________________________________继续。在说步骤三前,多说一句,存下步骤二的应急钱,这一步很重要,这一步没完成前,所有投资、理财都是奢谈,更不用说花钱、享受。还有随着你的情况改变,每月花费增多,这份半年的应急钱也要随之增加。如果减少呢?不减!(没办法,我就是这么简单粗暴明了)步骤三:(财务保障阶段)存下自己理财保障金。哥们,你TM在逗我吗?你前面明明说了“储蓄全都是输家的策略”,你咋还让我存钱????好吧,哥们你骂得对,我也发现我有点......请无情的折叠我吧......不过还是先请你存下你的财务保障金:你一年生活费。加上步骤二:半年生活费的应急钱。就是你要存下18个月的生活费。继续上面的例子每月2500。18个月就是4.5万元。这里有本人血的教训,回忆起来眼泪哗啦啦的流啊/(ㄒoㄒ)/~~/(ㄒoㄒ)/~~/(ㄒoㄒ)/~~本人青春年少时,刚步入社会,工作开始其实只用的不到1年的时间就完成了步骤二,存下了半年的生活费。然后春风他就得意了,意气他也风发了。开始理财吧!开始投资吧!股票、期货咱也学学吧,兄弟要开个店,咱也入个股吧.......然后......就重复步骤二吧......(┬_┬)(这里我犯了个严重的错误,我拿了应急钱进行投资)你会说,那是你没经验,我们都老江湖了,不会亏!你老江湖了,咋财务还不自由,咋还来看这个问题?财务自由和你会不会赚钱、会不会亏钱,没啥关系。更多的是,你收入和花销比。你月入10W,全花完了,到月底还要找别人借钱......让我想起了咬耳哥拳王泰森,这哥们曾经的收入超过80%知友吧(好吧别笑,90%......92%......95%.....随便几了)他财务自由了吗?莫说自由,有我们现在谈的步骤三财务保障吗?其实泰森(当时的)连步骤一 财务自知 都没完成。所以先继续老老实实的存钱,使劲存,我后来有几个月只花600,其他都存了,我舍友都不落忍了,说:兄弟,你再这样下去,可别搬走了啊,我一人可租不起这800一月的房,我这有几包没用完的方便面调料包拿去拌个汤吧,别噎着....这18个月的生活费,也就是财务保障金也请存在银行里,不要用,它不是给你投资的。它能给你的是更大的安全感。在任何的投资失误情况下,它可以让你很容易的重新开始,可以很自豪的说:老子以后再喝方便面汤,也要放火腿肠啦!
+又或者你失业了、打球骨折上不了班了……至少可以安全、平稳地生存18个月。你可以在一年半的时间里,去整理你的思绪,规划和选择你今后想要做的事、喜欢做的事、适合做的事。有了这一年半的缓冲期,就不会乱了方寸,坏了阵脚。难道说只存钱,不过日子了?去捡别人的方便面调料包才是对的?当然不是,我那是对自己初次投资失败的惩罚。不要变成一个守财奴。我们要做的是:在保证自己不降低目前生活水准的前提下理财(当然,你是个月光欠费族,该消减开资,还是要减)。你原来每月花2500,在出现失误、事故的情况下还能每月花2500。所以要完成步骤二、三真的只要存钱就够了:定期存钱。每月存下收入的20%(好吧降到10%,再少就都花了吧,别理财了),应该不是难事。不行,就办个定存,工资一到账上,银行自动帮你扣掉一部分钱存定期的那种。在存下自己理财保障金前,请保证你的每月花费正常,保证在2500左右浮动,我允许你这个月份子钱多了,菜价涨了....这都正常。但是别突然说:老子要买个车、我看上个LV包包了......好吧,你工作需要,你就买吧,你能不动理财保障金的钱,随便买。其实我想说的是作为一个有存钱习惯的中国人,大多可以轻松完成步骤二。在老一辈人里面,完成步骤三也很正常。但是,像我当时26岁前就能完成步骤三的(非富二代)年轻人,(就不谦虚,你能把我怎么滴)应该不到一半。<( ̄▽ ̄)> 哇哈哈…完成步骤三换工作和完成步骤二换工作,是不一样的。完成二,你可以和原单位硬气:老子不干了。完成三:你就可以和应聘单位硬气,你完全可以对应骋单位挑肥捡瘦,开出你的条件和价码。钱有时候真的是你的底气,银行里有多少钱,你表现出来的气场真的不同,面试官看的出来,相信我。 步骤三完——待续,补充一句:理财的首要目标是达到拥有足够维持闲居生活的资产,并非成为大款。 _______________________________________________________________________________我们继续,上面排名第一的答案说:当一个人的非工资收入≥总支出,则该者即便达到了财务自由。其实在我的计划里,这只完成了我的第四步——步骤四:(财务自足阶段)使自己的非工资收入≥总支出。达到财务自足很难吗?
+说个我兄弟25岁就达到财务自足的故事:
+我有个兄弟大学一毕业,23岁就和在学校勾搭的妹子结婚了。两个人在一个二线城市,有点混不下去,工作不好找,苦闷窝在租来的屋子里打WOW,非常难得的是他家妹子没有嫌弃他,而是和他一起窝在家里打WOW,组队打。就这样,他俩靠着家里的接济,和时不时打点零工,混到了24岁。也算省吃俭用,两个人每月开支2000元,还要给WOW充点卡。
+终于,这哥们想,不能继续这么下去了,总要解决生存问题,努力吧!于是,在他24岁末的最后一个月,他们双方父母,合资给他们在所在城市买了套140平的大房子,一次付清。他俩,把这套房子每月3500元租了出去,自己仍旧租原来住的单间,夫妻俩继续窝着打WOW,零工都不打了。于是我这兄弟在25岁的时候,财务自足了。是的,这不是我兄弟的故事,不过这的确是对达到财务自足阶段的夫妻俩,只要他们不在WOW乱买装备。忘了上面的故事吧,看看我们自力更生的大多数。完成步骤三后,可以接触些稳定型的理财产品了。不过,还是想先用最安全的银行利率来做说明:这是今天从网上截图的国有银行存款利率。这是今天从网上截图的国有银行存款利率。
+请看第一项,活期年利率0.35。
+下面请做数学题,你要在银行存多少钱活期,才能靠吃利息,保证我上面的每月2500元开销???? 求?解得= 8571428 . 571428571 元就是说你要在银行存857万元人民币的活期(当然这钱你是不能动的),就算你不去工作,银行也可以每月给你2500元花,你这个月花完了,下个月继续给你2500.虽然这个数字有点坑爹,但是的确,你的钱已经在帮你生钱养你了。好吧,857万是个坑爹的数字,所以我们前面才说“储蓄全都是输家的策略”利率太低了。换个柔和点的,来看看现在大家熟悉的余额宝。这是昨天的余额宝年利率。我们重复上面的公式: 求?解得= 560014.9337315662 元好了56万,大家轻松多了吧,存个56万不是难事吧。其实还有收益更高一些,且相对稳定的理财产品,为了不做打广告之嫌,就不在这说了。
+所以我说使自己的非工资收入≥总支出并不是我最终的财务自由。因为我已经完成了,没啥难的。至于要如何存入56万元,那是你的事,我这不是在教你怎么赚钱,只要你不违法就行。你可以靠工资收入(这个比较慢)、做生意、低风险的投资(别动步骤二、三的钱)、像上面夫妻俩靠父母给......说财务自足并不是财务自由,是因为这份收入并不稳定。比如上面夫妻俩,突然有孩子了,一个月2000不够花了,那他们就不能靠父母给的房子继续WOW了,趁早告别部落和联盟吧。物价会涨、利率会调(余额宝利率都连续跌多久了不是?),还有那该死的通货膨胀!!!!!!这些都要求你不断追加本金。而且56W?买俩车吧?你就呵呵吧。虽然达到财务自足阶段,你已经可以比很多人活的轻松,活的舒服,甚至可以不用找工作,老主顾、新主顾的脸都不用看了。但是请不要满足于财务自足阶段,让我们向更高级的步骤五迈进。——待续,一样给个待续小结:财务自足只能让你活的很安全,活的很有保障,并不能让你活的滋润和精彩。所以这阶段钱(就是那56W)的去处,请放在稳妥型的地方(货币基金之类),千万不要放到股市、期货这些高风险的地方。(你拿固定的一点钱玩玩,也不反对)———————————————————————————————————————————
+天亮了,继续。财务保障到财务自足过程,也是个不断存钱的过程。中间你当然可以把每月余钱,做以下安排:1、全部存下;2、一部分雷打不动的存下,一部分进行一些其他高回报的投资,(投资都有风险,都要自己把握和承担)后果也无外乎两种:
+a.投资获得收益,加速你完成步骤四(本来要五年,现在两年就完成了);#^_^#奇怪,我干嘛要脸红...
+b.投资失败,推迟实现财务自足(现在要用八年了)。上面两种方式,你自己按个人能力选择。但是绝对不要选方案 3、全部用来投资。我们现在进入更高的一个阶段步骤五:(财务舒适阶段)使自己的非工资收入≥总支出的120%上面说了,完成了财务自足,你现在的余额宝里有了56万 ,但并不能让你活的滋润、精彩。因为不确定因素实在太多了,比如说父母生病了、二胎普及,太太又有小宝宝了-_-|||、投资出现重大失误、菜价涨价了、余额宝利率降到4.5了……于是我们希望再好上加好。上面算过,我们余额宝里有56万元,按5.357的年利率,每月能生出2500元利息,每年就是3万元。那么比3万元再多出20% ,就是3.6万元。那这时候我要存多少,才能生出这3.6万元?56直接×1.2呗,是......恩......等下......我找下计算器......啊.....是67.2万元!好吧,完成了上面步骤四财务自足,我还一直说,注意消费、别把钱乱投资、买车什么的要考虑好......我好烦人啊.....我都烦我自个....到了现在财务舒适阶段,我们能做什么?(当然还是保证维持原来每月2500生活水平的基础上)
+我们已经可以在不用出去工作的情况下(理论上是这样,但天天玩其实是个很痛苦的事,真的!反正我们已经可以说我们是为了精神富足、自我提升而工作,而不是完全为了钱工作),还能够维持你基本的生活,而且,还有多余出来的20%的钱,用于比原先生活水平高一些的花费、享受,以及用这多出来20%的钱进行一些风险高一点的投资。从步骤一到步骤五,我们其实并没有用到什么太过高深的财务技巧,方法只有一个:就是——存钱!持续且安全的存钱!…… 然后嘞?就此止步?心安理得的窝着WOW?不想继续一窥我们最初目标“财务自由”的境界?——待续 ,不小结了。
+———————————————————————————————————————————清明扫墓归来,继续。因为本人目前只完成步骤五,所以六、七还是目标,不敢说太多。步骤六:(财务解锁阶段)使自己的非工资收入≥总支出的10倍先说说,本人对达到这个的阶段后的YY:
+每日都可以不做讨厌事,不看恶心脸;成天事少钱多离家近,权重位高责任轻;还要睡到自然醒,吃成水桶腰……...(好恐怖)好像某个电视剧里有个富二代说过:“这不是我想要的日子!”(画外音(#‵′)凸:“这TM是我们想要的日子!”)放心吧,只要你是靠自己努力达到这个阶段的,工作绝对会成为你戒不掉的一个习惯(不管是打工,还是创业,又或是自由职业)。先看数字:不用公式了,直接把步骤四的钱x10就好。就是说你在余额宝里有560万人民币就达到这个目标了,你每月一天活不用干,照样有两万五给你花,花完下个月还有。但是要达到这个阶段,还是像前面几个步骤一样,光靠每月存下 百分之几 的死工资,这个.......也行.....就是时间有一丢丢的长.....开始学习和接触一些投资工具吧,(原则不能破:1、完成步骤三前不进行高风险投资;2、步骤四、五阶段可以适当投资;3、不要动步骤二到五里的本金。)可以选择工具有很多,回报和风险不同。因为我不在这个阶段,还没资格推荐。要自己去分析和学习,也可以请教专家(但是先要认清他是不是真的专家)。其实,达到财务舒适阶段之后(就是有67万之后),我们已经可以用每月多处的20%的钱,承受较高的风险了。最差情况,也不过是这些钱全部亏掉。只要本金67万,不动,我们就不会降低生活水平。笨点如我,交了半年几个月的学费,持续总结失败经验,找到适合自己的投资策略,坚定执行,你也不会一直亏(真一直亏,继续都存了吧)。投资方面,不敢做经验介绍。消费方面,倒可以说一点:大额消费(车房之类),永远不要一次付清,请你贷款或办分期。 本金,随便放个定投工具里(利息高过分期利息的工具有不少,不知道学习去),分期利息省掉不说,还能多赚几个点的利差。还有就是和不同层次、三教五流的朋友交往与交流的必要性,不是说只和高层次的,真是什么人(线下,不是网上)都要玩。虽然现在网络发达,但再发达......昨晚我几个朋友一起吃饭,一个兄弟捞到我们城市下属县的一个项目,分我们几块肉吃。这个在自己身边,稳赚不赔的短期投资小项目,网上搜死都搜不到。就算搜到也没用,你不可能大老远从京城来我们二、三线的小地方来入个股,来了也不给。再就是,外地的朋友,偶尔来玩,也会不经意地介绍一些信息。很多信息,其实每个人都能用,但很多人都没在意。比如:去年之前,是可以用支付宝给信用卡套现的。我以前用这一点,每月小收入几百到一千+,现在没了。还有就是要低调,不要臭屁(好吧,我前面说的都是骗人的,我可穷了)。因为我们在学会赚钱,收入提高的同时,还要用极大的耐心来控制欲望,继续过原先每月2500的生活。开源节流,确保我们的支出低于我的资产收入,永远是达到财务自由的一个前提。支出增加,你财务自由的本金也要增加。你每月真要花2.5万,我财务解锁阶段的本金,也不过够你实现财务自足。等至少实现第五步——财务舒适阶段之后,再开始慢慢享受人生。到了财务解锁阶段,我们每月有2.5万,每年有30万,给你超额的消费,和进行高风险的投资。这时离我们最初目标,也只有一步之遥。——待续最终一步“财务自由”———————————————————————————————————————————
+最近有点懒,趁现在下雨,出不了门,更新完。步骤七:(财务自由阶段)使自己的非工资收入≥总支出的12倍? NO !其实最后一步不写也罢,有点遥远......,我定的财务自由标准有点高。财务解锁,还是有希望达到的,自由就......(你妹,逗我吗?!不是说一步之遥吗?)好吧,哥几个也可以把财务自由的标准定为:使自己的非工资收入≥总支出的12倍。你若已经实现“财务解锁”,也就是10倍,再接着实现12倍这一标准,真也就是一步之遥。不过太无聊,太容易了不是?富爸爸还说财务自由是使自己的非工资收入≥总支出呢,任何词的定义都是人给的,他能给,我咋就不能给?我又不想拿经济学学位,不想背标准答案。我心里达到财务自由的人有谁?比尔·盖茨、李嘉诚、马云......都算。他们有足够的钱,自由的买自己所需的商品、服务、教育,甚至一个看好的企业......好吧,太远的事,不知道就不要YY。我们轻松点吧,就当使自己的非工资收入≥总支出的12倍时就实现财务自由了吧。——————————————————————————————————————————
+总结:以上七步,并没有教大家怎么去赚钱,其实个人认为,理财和实现财务自由(容易的那个)更多的是一种习惯,你月入5千的,并不比一个月入5万的人更难实现财务自由。(他入5W,花5W,没存款)做个申明:上面七个步骤,所涉及的具体数字、百分比、做法、规定,适合且只适合本人(其实我也没完全照章实施)。我只是展示出,个人粗陋的理财想法,各位不用照搬照套,理财还是要结合各自的实际情况,找到适合自己的,才是自己的理财之道。稍微说说接触过的一些工具:
+A、有价证券,如:基金、股票、期货、外汇(后面三个,基本不玩了,不适合我,只是留着以前放在里面的钱,偶尔看看,没提现,也没继续投入);
+B、房产,跟着几个前辈,主要三个玩法:
+1、直接入股新盘,早几年年利率甚至可以到50%。当时直接被这么高的利率吓到,没敢借钱投,只是动了自己的积蓄。(现在也没后悔,原则不能破)这种机会,现在没有了。
+2、早年,贷款买了两套单身公寓,已租养贷,收租非常烦,就丢给中介打理了,现在卖了一套。
+3、自己住的房子,用房产证贷款投资,利息绝对赚的回来,但为安全,多投在1分多的地方,每年小赚几个点。
+C、公司(有几个纯粹是身边人的小打小闹)
+第一个,血本无归(见步骤三)。后来,眼光尖了点,一定要挑,不成气候的就不投。
+有一次,一个非常好兄弟面上实在过不去,也知道他撑不起来,就说:我不投了,算我无息借你的,也不参与管理。半年后关门,但兄弟还是全款还了我,借钱也要挑人,真的。
+有一次,是跟一同事投一酒店。两年后倒闭,退股,两年没分红,亏了10个点,有以小股东身份去白住了两天。也还好,风险一开始就有估计到,在承受范围内。
+基本上,投成功的,一般每年也就在10到20个点左右。还有其他的,身边有人玩,自己没碰,比如:出版、网站、中介、专营等等……这些多少都要投入时间参与管理,就没碰。最后:每个人的欲望、赚钱能力、存款的自觉性都不相同,但我们在理财上能做相同的努力,可以归纳为那个老词——开源节流。祝各位心想事成。 ——终 ————————————原来专栏不是每个人都能写的 ( ̄. ̄) 分割线———————————同学提问:今年大四,想问更多的关于存钱之外的思路。想问您作为大学生在存钱之余,准备投资之前,能不能有什么资料看的?作为非经济类得人想要做一些资本运作之类的投资,根本无从下嘴啊,而且感觉很多比如买两套用贷款买房子再租出去还房贷这种方法好像很难接触到。(( ̄y▽ ̄)╭说我长的帅的话就不贴出来啦,留着自己看啦~~~)总结问题:
+1、学生党,除了存钱还能做什么投资理财?
+2、有什么有关投资的资料可看?
+回答:
+一、学生党和工作新人除了存钱还能干啥?
+作为一个吃饭靠爸,花钱靠妈的学生党,其实还没资本投资,存个钱都奢侈。更不建议,拿爸妈的钱来投资,因为你爸给了你1万元,你拿来放在余额宝里,每年赚5个点(现在没有了,就4点多了),挺开心。我要说你是个好孩子,比那些钱一到手就进夜店的孩子好太多了。但你爸本来可以把这1万拿去放2分贷的。(啥?!你爸没那头脑??( ̄ε(# ̄)☆╰╮o( ̄皿 ̄///) 坏孩子,成年了,拿父母的钱就不对!!)
+大四生首要任务,是找工作。找到之后,还有精力也不是投资,而是开源赚钱。(对完成步骤三以前的同志们这点同样适用)开源赚钱,增加收入,加快步骤四、五的完成,早日进入投资时代!!!投资投资,没有资,拿啥投?我当然不会告诉你,我大四把一年学费5000大洋借给我舍友家的企业(他向好多同学借了),借了一年,这期间,提心吊胆,一直瞒着父母。终于在临毕业前,收回本息6000元,然后对自己说,老子再也不把学费借人啦。后来我也遵守了我的承诺,工作十年来再也没把自己的学费借人<( ̄3 ̄)> 。因为这是个坏例子,告诉你们,你们可能会产生借钱放贷的冲动,所以不能告诉你们。
+我可以告诉你们的另一个例子是,我大二时,另一个舍友,借了全系20几个兄弟姐妹(没错姐妹!连同系妹子的钱他都借了,真不是人!姐姐是谁?是我们老师啦)的钱,几十到几千,总共也就几万,呵呵呵。然后,大三他就不来了,我们都没要利息......后来老师姐姐,好像是把他大一时交的学费拿来抵了,至于我们其他兄弟妹的钱就一直在借的进行时~~~~
+说这个例子是想说:1、刚入社会的新人们,其实不太具备对 投资风险 的评估和承受能力。那为什么,我大二被坑了一次,大四还敢继续(虽然投资成功)?讲感情呗!不善拒绝呗!不理性呗!我当时真不是冲着20个点的利息借钱的!!!你们要相信我!!2、因为本金太少,虽然年利率不低,但用几乎全部家当一年也只能赚1000元,对一个大学生来说,其实是很不划算的。那有没有学生就可以做的增加收入的事?有!不出宿舍就可以!真有,我前几个阶段就做过,现在考虑时间成本以及享乐主义的养成,已经不做了。那么下班了........待续——————————我是劳模分割线————————————————继续......细节请看 业余时间充足的 学生党 和 工作新人,怎么增加额外收入?讲完,谢谢,下一个问题......
+二、有什么有关投资的资料可看如果是要推荐具体哪本理财书籍,非常值得看,看了就改变了我什么......好像没有~~~~其实我有一直在看书,现在也还在看(看我文字这么凌乱,就知道了~~~)早期也看,各种排行版上的各种理财书,还买了大部头的《投资学》(好想上图,给大家看看我做的笔记,讲一大堆美国复杂的投资工具,看一半次贷危机就来了......妹的,亏我记得那么认真,对我而言,就这本书看了最没用)。要说哪本起到很大作用?没有!(非要说有,就是《富爸爸》给了我理财启蒙)现在看资料,都是碰上具体事情,才会有针对的去看。比如买房的时候,就猛翻各种房产投资书籍、上网也搜,还有贷款相关的(其实大多也就过过眼,根本不可能看完,在书店翻二十本,看到一本好的,就回家上网下载去了,下不到就上网买,我不是有会员账号......)
+再比如,今年接触P2P,好像没有找到相关书籍,就上网搜各种讨论帖子,用户评价,关键还是身边用过朋友的评价,比较直观。然后再亲自试试。其他书,还用看吗?要啊,不看怎么装逼......不是,看书和逛知乎一样,扩展一下见世面,好玩而已,虽然不一定用的上。只看,看的下去的就好。三、个人:同学问我个人常用的投资,其实不是很推荐,想知道告诉大家也无妨——民间借贷。
+不推荐的原因,大家也都清楚。虽然平均收益2个点(就是年利24%),但身边坏贷、烂贷的例子层出不穷。还是那句话:实现步骤五之前,一切求稳。谢谢————————————————————2015.2.10——————————————————今天没有更新,未防止误导新人,和广告嫌疑,删除上文两段贷款方面的理财方案。本人也没有备份,不好意思啦╮(╯▽╰)╭
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+收入来自于两部分:一部分是工作收入,工资薪金和养老金、年金等;一部分是投资收入,进行金融投资或实业投资的投资所得。
+
+大部分人每月支出主要来自工作收入,没工作收入生活陷入困境。但如果投资收入够多,不用工作就可过好日子,随时可以退休或可以睡觉睡到然醒,就达到财务自由的境界了。当达到财务自由时,投资收入将成为个人收人的主要来源,不再为赚取生活费用而工作。
+
+理财的最终目标是追求财务自由,财务自由的定义是:不靠工作收入,靠理财收入就能过生活。要求每年的理财收入大于生活支出,这对没工作的退休人士特别重要,理财规划师也特别对即将退休的人在财务上有这样的要求。
+
+要达到财务自由是逐步完成的,中产阶级人士透过股票、债券、保险等投资获取现金流。
+
+比如你生活费用一个月要一万元,一年要12万元,这就是你的年生活支出。你现在有第一个100万,放在理财型保险,每年生存金加分红有4%收益率,一年有4万元,这是不够生活支出的。
+
+再存了第二个100万,放在高评等债券投资,每年利率6%,一年就有6万元,跟保险收益加起来10万元,还是达不到12万元的财务自由门坎。
+
+再努力储蓄50万,放在蓝筹股,每年发放现金股利8%,一年就有4万元,跟保险债券收益加起来14万元,就达到财务自由。
+
+{% highlight text %}
+{% endhighlight %}
diff --git a/_drafts/2015-08-04-mysql-myisam_init.md b/_drafts/2015-08-04-mysql-myisam_init.md
new file mode 100644
index 0000000..c4d7731
--- /dev/null
+++ b/_drafts/2015-08-04-mysql-myisam_init.md
@@ -0,0 +1,350 @@
+---
+Date: October 19, 2013
+title: MySQL MyISAM
+layout: post
+comments: true
+language: chinese
+category: [mysql,database]
+---
+
+
+
+
+
+* 文件比较小;
+* 支持 FULLTEXT、GIS,不过性能不太好;
+* 支持复合索引以及自增主健(不建议使用);
+* 表级锁,对性能影响较大;
+* 不支持事务;
+* 不支持表恢复(通过 REPAIR TABLE);
+* update 效率较低,容易产生碎片。
+
+
+
+.MYI 索引
+.MYD 数据
+
+
+--- MyISAM file structure
+
+记录在数据文件中依次存放
+ Delete -> Gap
+ Gap -> Next Insert, or Append
+FIXED vs. DYNAMIC vs. COMPRESSED
+ Fragmentation (String extension)
+
+Index: record Number (fixed), byte offset (DYNAMIC)
+ Data pointer size variable
+ myisam_data_pointer_size=6(256TB)
+alter table t min_rows=n max_rows=m avg_row_size=p
+
+--- MyISAM update storm
+update t set sth = concat(sth, sth)
+split record = seek
+move to end of table = all indices updated
+either way: fragmentation, seeks
+
+
+--- MyISAM file structure
+update t set count=count+1, and server crash?
+some records update, you do not know which.
+recover from unchrashed slave, or have invalid data.
+also, the table is marked as crashed.
+
+--- Compressed MyISAM
+Close all filedhandles: FLUSH TABLES
+run myisampack to compress data file: invalid index.
+run myisamchk to rebuild index.
+data footprint approx. 50% smaller.
+ read-only
+
+--- MyISAM locking
+table level locks: shared(read), exclusive(write)
+many concurrent select statements, serialized write statements.
+writes have precedence
+running statements have precedence.
+
+select high_priority ...
+write (insert update delete, ...)
+select ...
+low priority (update low_priority ...)
+
+many selects, some slow (4s runtime)
+foreach $i (@stmt) { $dbh->do($i) }
+ insert blocks subsequent reads.
+ insert blocked by runing(slow) read.
+one insert every 4s.
+
+
+--- Explicit locking
+lock table t1 read, t2 read, t3 write.
+all locks taken at once: avoids deadlocks.
+unable to touch unlocked tables during lock: ensures correctness and completeness.
+unlock tables (or disconnect)
+
+how does explicit locking affect the myisam interlock scenario?
+locking can actually make things fasster.
+but will increase latency
+
+--- Tuning MyISAM
+MyISAM caches myi data, but no myd data.
+system must have sufficient buffer cache: mysqld size must be limited.
+key_buffer_size=...
+ideally: sum of all myi files.
+
+key_cache_division_limit = 100
+the division point between the hot and warm sublists of the key cache buffer list(percent)
+
+alter a server restart, the key cache is cold.
+ load index into cache , , ...
+this preloads the cache w/ linear disk accesses.
+much faster than normal warmup.
+
+handling myisam index update storms:
+ delayed_key_write = OFF|ON|ALL
+ create table t (...) delay_key_write = 1;
+status variable key_blocks_not_flushed.
+large number of unflushed blocks + crash = monster repair table.
+
+myisam_recover_options = force,backup
+ may not be enough in the face of delayed key writes
+myisam_repair_threads = 1
+ used to be broken with > 1
+
+--- monitoring myisam
+table_locks_immediate
+ table locks granted without waiting
+table_locks_waited
+ table locks granted after wait.
+1%=dangerous, 3%=deadly
+
+--- myisam maintenance
+show table status - data_free
+optimize table to reclaim data_free
+ this is very slow and x-locks.
+analyze tables - optimizer statistics
+ also very slow (in myisam)
+
+
+
+## 文件
+
+总共有三类文件:.MYD (数据文件,MySQL Data)、.MYI (索引文件,MySQL Index)、.frm (格式文件,Format);而数据文件的行格式按照不同的类型分为了 fixed、dynamic、packed 三种。
+
+### fixed format
+
+这个时默认格式,当表不含变长字段(varchar/varbinary/blob/text)时使用,每行固定,所以很容易获取行在页上的具体位置,存取效率高,但是占用磁盘空间较大。
+
+* Page Size:MyISAM 没有像其它数据库一样保存数据时采用页存储,这也就意味着,你不会看到行间的填充字符。
+
+*
+
+{% highlight text %}
+CREATE TABLE foobar (c1 CHAR(1), c2 CHAR(1), c3 CHAR(1)) ENGINE=MyISAM;
+
+INSERT INTO foobar VALUES ('a', 'b', 'c');
+INSERT INTO foobar VALUES ('d', NULL, 'e');
+{% endhighlight %}
+
+
+
+
+
+# 锁机制
+
+MySQL 有三种粒度的锁:表级锁、行级锁和页面锁;其中 InnoDB 支持表锁和行级锁,MyISAM 支持表级锁,表锁分为读锁 (read lock) 和写锁 (write lock),接下来我们看看 MyISAM 引擎的表锁。
+
+为了测试,首先创建如下的表。
+
+{% highlight text %}
+CREATE TABLE foobar (id INT, name CHAR(30)) ENGINE=MyISAM;
+INSERT INTO foobar VALUES (1, 'foo'), (2, 'bar');
+
+CREATE TABLE test (id INT, name CHAR(30)) ENGINE=MyISAM;
+INSERT INTO test VALUES (1, 'test1'), (2, 'test2');
+{% endhighlight %}
+
+## 读锁 (read lock)
+
+当一个会话加读锁后,其它会话是可以继续读取该表的,但所有更新、删除和插入将会阻塞,直到将表解锁。
+
+{% highlight text %}
+mysql[s1]> LOCK TABLE foobar READ; # s1中对表加读锁
+Query OK, 0 rows affected (0.00 sec)
+
+mysql[s2]> SELECT * FROM foobar; # s2中可以对表正常读取
++------+------+
+| id | name |
++------+------+
+| 1 | foo |
+| 2 | bar |
++------+------+
+2 rows in set (0.00 sec)
+mysql[s2]> INSERT INTO foobar VALUES (3, 'test'); # s2中的写入被阻塞
+
+mysql[s1]> UNLOCK TABLES; # s1中释放锁,s2写入成功
+Query OK, 0 rows affected (0.00 sec)
+{% endhighlight %}
+
+MyISAM 会在执行 SELECT 时会自动给相关表加读锁,在执行 UPDATE、DELETE 和 INSERT 时会自动给相关表加写锁;对于表级读锁有几点需要特别注意的地方 (InnoDB相同):
+
+### 1. 锁表时要将所有表加锁
+
+锁表时一定要把所有需要访问的表都锁住,因为锁表之后无法访问其他未加锁的表。
+
+{% highlight text %}
+mysql> LOCK TABLE foobar READ;
+Query OK, 0 rows affected (0.00 sec)
+
+mysql> SELECT * FROM test;
+ERROR 1100 (HY000): Table 'test' was not locked with LOCK TABLES
+{% endhighlight %}
+
+### 2. 读锁后不能更新
+
+当前会话对表加读锁之后,该会话只能读锁住的表,而无法对其进行 UPDATE、DELETE 和 INSERT 操作。
+
+{% highlight text %}
+mysql> LOCK TABLE foobar READ;
+Query OK, 0 rows affected (0.00 sec)
+
+mysql> INSERT INTO foobar VALUES (4, 'again');
+ERROR 1099 (HY000): Table 'foobar' was locked with a READ lock and can't be updated
+{% endhighlight %}
+
+### 3. 对表多次加锁
+
+同一个表如果在 SQL 语句里面如果出现了 N 次,那么就要锁定 N 次,否则会出错。
+
+{% highlight text %}
+mysql> LOCK TABLE foobar READ;
+Query OK, 0 rows affected (0.00 sec)
+
+mysql> SELECT * FROM foobar f1, foobar f2 WHERE f1.id = f2.id;
+ERROR 1100 (HY000): Table 'f1' was not locked with LOCK TABLES
+
+mysql> UNLOCK TABLES;
+Query OK, 0 rows affected (0.00 sec)
+{% endhighlight %}
+
+应该使用如下的方法:
+
+{% highlight text %}
+mysql> LOCK TABLE foobar AS f1 READ, foobar AS f2 READ;
+Query OK, 0 rows affected (0.00 sec)
+
+mysql> SELECT * FROM foobar f1, foobar f2 WHERE f1.id = f2.id;
++------+------+------+------+
+| id | name | id | name |
++------+------+------+------+
+| 1 | foo | 1 | foo |
+| 2 | bar | 2 | bar |
+| 3 | test | 3 | test |
++------+------+------+------+
+3 rows in set (0.00 sec)
+
+mysql> UNLOCK TABLES;
+Query OK, 0 rows affected (0.00 sec)
+{% endhighlight %}
+
+## 写锁 (write lock)
+
+当一个会话给表加写锁后,其它会话所有读取、更新、删除和插入将会阻塞,直到将表解锁。
+
+{% highlight text %}
+mysql[s1]> LOCK TABLE foobar WRITE; # s1中对表加写锁
+Query OK, 0 rows affected (0.00 sec)
+
+mysql[s2]> SELECT * FROM foobar; # s2的读请求将会被阻塞,直到锁释放
+{% endhighlight %}
+
+同上,锁表的时候一定要把所有需要访问的表都锁住,因为锁表之后无法访问其他未加锁的表。
+
+## concurrent_insert、local 操作
+
+注意,这两个操作操作是 MyISAM 引擎所特有,InnoDB 无此功能。如上,当给表加锁之后,其它会话的写入将会被阻塞,通过 local 关键字,可以让其它会话也能添加数据。
+
+上面所说的功能还需要配合 concurrent_insert 全局变量使用,该变量有三个值:
+
+* NEVER(0):加读锁后,不允许其它会话并发写入。
+* AUTO(1):加读锁后,在表里没有空洞(就是没有删除过行)的条件下,允许其会话并发写入。
+* ALWAYS(2):加读锁后,允许其它会话并发写入。
+
+可以通过如下命令可以查看当前数据库的设置:
+
+{% highlight text %}
+mysql> SHOW GLOBAL VARIABLES LIKE 'concurrent_insert';
+{% endhighlight %}
+
+接下来测试一下。
+
+
+{% highlight text %}
+mysql[s1]> SET GLOBAL concurrent_insert=ALWAYS;
+Query OK, 0 rows affected (0.00 sec)
+
+mysql[s1]> LOCK TABLE foobar READ LOCAL;
+Query OK, 0 rows affected (0.00 sec)
+
+mysql[s2]> INSERT INTO foobar VALUES (4, 'again');
+Query OK, 1 rows affected (0.00 sec)
+mysql[s2]> SELECT * FROM foobar;
++------+-------+
+| id | name |
++------+-------+
+| 1 | foo |
+| 2 | bar |
+| 3 | test |
+| 4 | again |
++------+-------+
+4 rows in set (0.00 sec)
+
+
+mysql[s1]> SELECT * FROM foobar;
++------+-------+
+| id | name |
++------+-------+
+| 1 | foo |
+| 2 | bar |
+| 3 | test |
++------+-------+
+3 rows in set (0.00 sec)
+{% endhighlight %}
+
+注意,此时 s1 对 s2 写入的数据是不可见的,只有当 s1 释放锁之后才可见。
+
+## MyISAM 锁的调度机制
+
+默认写锁优于读锁,当有大量的对同一个表的读写请求时,只有在所有的写请求执行完成后,读请求才能获得执行机会。着就会导致,一个大量更新的表,将会无法读取数据。
+
+### 手动设置优先级
+
+此时可以使用 LOW_PRIORITY、HIGH_PRIORITY 和 DELAYED 关键字,来缓解这一问题;在执行 DELETE、INSERT、UPDATE、LOAD DATA 和 REPLACE 的时候可以使用 LOW_PRIORITY 来降低该更新语句的优先级,让读取操作能够执行。而在执行 SELECT 时,可以使用 HIGH_PRIORITY 来提高该语句的优先级,让读取操作能够执行。
+
+另外,可以在执行 insert 和 replace 的时候可以使用 DELAYED 让 MySQL 返回 OK 状态给客户端,并且修改也是对该会话可见的。
+
+不过,上述方法并非已经将数据插入表,而是存储在内存等待队列中,当能够获得表的写锁再插入。优点时客户无序等待,提高写入速度;坏处是,无法返回自增 ID,系统崩溃时会导致数据丢失。
+
+### set LOW_PRIORITY_UPDATES = 1
+
+让所有支持 LOW_PRIORITY 选项的语句都默认地按照低优先级来处理。
+
+### MAX_WRITE_LOCK_COUNT
+
+该变量默认为 int 最大值,表示当一个表的写锁数量达到设定的值后,就降低写锁的优先级,让读锁有机会执行。
+
+### 查看锁竞争
+
+直接通过如下命令查看。
+
+{% highlight text %}
+mysql> SHOW STATUS LIKE 'table_locks%'
+{% endhighlight %}
+
+# 参考
+
+相关内容可以参考官方文档 [MySQL Internal Manual - MyISAM Storage Engine](http://dev.mysql.com/doc/internals/en/myisam.html)、[MySQL Reference Manual - The MyISAM Storage Engine](http://dev.mysql.com/doc/refman/en/myisam-storage-engine.html) 。
+
+
+{% highlight text %}
+{% endhighlight %}
diff --git a/_drafts/2015-08-17-network-timeout-retries_init.md b/_drafts/2015-08-17-network-timeout-retries_init.md
new file mode 100644
index 0000000..2db99df
--- /dev/null
+++ b/_drafts/2015-08-17-network-timeout-retries_init.md
@@ -0,0 +1,334 @@
+---
+Date: Auguest 05, 2015
+title: Linux 网络超时与重传
+layout: post
+comments: true
+language: chinese
+category: [linux, network]
+---
+
+在此介绍重传,为了保证可靠性,在 TCP 的三次握手、数据传输、链接关闭阶段都有响应的重传机制。那么,重传的次数都是有那些参数指定?tcp_retries1 和 tcp_retries2 到底有什么区别?什么是 orphan socket ?
+
+
+
+
+首先我们查看一下网络中与重传相关的参数有那些。
+
+# 重传简介
+
+关于重传的控制参数,可以查看内核中的 Documentation/networking/ip-sysctl.txt 文件,内核中的控制参数总共有五个相关参数。
+
+{% highlight text %}
+tcp_syn_retries - INTEGER
+ Number of times initial SYNs for an active TCP connection attempt
+ will be retransmitted. Should not be higher than 255. Default value
+ is 6, which corresponds to 63seconds till the last retransmission
+ with the current initial RTO of 1second. With this the final timeout
+ for an active TCP connection attempt will happen after 127seconds.
+
+tcp_synack_retries - INTEGER
+ Number of times SYNACKs for a passive TCP connection attempt will
+ be retransmitted. Should not be higher than 255. Default value
+ is 5, which corresponds to 31seconds till the last retransmission
+ with the current initial RTO of 1second. With this the final timeout
+ for a passive TCP connection will happen after 63seconds.
+
+tcp_orphan_retries - INTEGER
+ This value influences the timeout of a locally closed TCP connection,
+ when RTO retransmissions remain unacknowledged.
+ See tcp_retries2 for more details.
+
+ The default value is 8.
+ If your machine is a loaded WEB server,
+ you should think about lowering this value, such sockets
+ may consume significant resources. Cf. tcp_max_orphans.
+
+tcp_retries1 - INTEGER
+ This value influences the time, after which TCP decides, that
+ something is wrong due to unacknowledged RTO retransmissions,
+ and reports this suspicion to the network layer.
+ See tcp_retries2 for more details.
+
+ RFC 1122 recommends at least 3 retransmissions, which is the
+ default.
+
+tcp_retries2 - INTEGER
+ This value influences the timeout of an alive TCP connection,
+ when RTO retransmissions remain unacknowledged.
+ Given a value of N, a hypothetical TCP connection following
+ exponential backoff with an initial RTO of TCP_RTO_MIN would
+ retransmit N times before killing the connection at the (N+1)th RTO.
+
+ The default value of 15 yields a hypothetical timeout of 924.6
+ seconds and is a lower bound for the effective timeout.
+ TCP will effectively time out at the first RTO which exceeds the
+ hypothetical timeout.
+
+ RFC 1122 recommends at least 100 seconds for the timeout,
+ which corresponds to a value of at least 8.
+{% endhighlight %}
+
+简单来说,五个重试参数的含义为:
+
+* tcp_syn_retries:在三次握手阶段,客户端发起链接时,发送 SYN 报文的重试次数。
+
+* tcp_synack_retries:在三次握手阶段,服务端回应 SYN+ACK 时,发送报文的重试次数。
+
+* tcp_orphan_retries:在关闭阶段,会影响到主动关闭链接的孤儿 socket 重传。
+
+* tcp_retries1:在数据传输阶段。
+
+* tcp_retries2:在数据传输阶段。
+
+在此重点关注一下 tcp_retries1、tcp_retries2 两个参数,以及 tcp_orphan_retries 。
+
+# tcp_retries1、tcp_retries2
+
+这两个参数在上述的介绍中有些模糊,可能由于过于概括,会令人产生很多疑问,甚至产生一些误解。
+
+1. 当重试超过 tcp_retries1 这个阈值后,到底向网络层报告了什么 suspicion ?
+
+2. tcp_retries1 和 tcp_retries2 对应的是重传次数?其中的时间是怎么计算出来的?
+
+3. tcp_retries2 应该是重传的上限吧,其中的 lower bound for the effective timeout 又是几个意思?不应该是 upper bound 吗?effective timeout 又是做什么的?
+
+## 源码解析
+
+定时器的初始化是在 tcp_init_xmit_timers() 函数中完成,超时重传相关的是 tcp_write_timer(),实际最终调用的是 tcp_write_timer_handler() 。
+
+{% highlight c %}
+void tcp_write_timer_handler(struct sock *sk)
+{
+ ... ...
+ switch (event) {
+ case ICSK_TIME_EARLY_RETRANS:
+ tcp_resume_early_retransmit(sk);
+ break;
+ case ICSK_TIME_LOSS_PROBE:
+ tcp_send_loss_probe(sk);
+ break;
+ case ICSK_TIME_RETRANS:
+ icsk->icsk_pending = 0;
+ tcp_retransmit_timer(sk);
+ break;
+ case ICSK_TIME_PROBE0:
+ icsk->icsk_pending = 0;
+ tcp_probe_timer(sk);
+ break;
+ }
+ ... ...
+}
+{% endhighlight %}
+
+如果是超时重传定时器触发的,就会调用 tcp_retransmit_timer() 进行处理,其中与 tcp_retries 相关的代码调用逻辑如下。
+
+{% highlight text %}
+tcp_retransmit_timer()
+ |-tcp_write_timeout() # 判断是否重传了足够的久
+ | |-retransmit_timed_out() # 判断是否超过了阈值
+ |-tcp_retransmit_skb() # 如果没有超过了重传阈值,则直接重传报文
+{% endhighlight %}
+
+接下来看一下 tcp_write_timeout() 的具体实现。
+
+{% highlight c %}
+static int tcp_write_timeout(struct sock *sk)
+{
+ struct inet_connection_sock *icsk = inet_csk(sk);
+ struct tcp_sock *tp = tcp_sk(sk);
+ int retry_until;
+ bool do_reset, syn_set = false;
+
+ if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
+ /* 超时发生在三次握手期间,重传次数通过tcp_syn_retries指定 */
+ ... ...
+ retry_until = icsk->icsk_syn_retries ? : sysctl_tcp_syn_retries;
+ syn_set = true;
+ } else { /* 超时发生在数据发送期间,也就是我们现在讨论的问题 */
+ /* 下面的函数负责判断重传是否超过阈值,返回真表示超过 */
+ if (retransmits_timed_out(sk, sysctl_tcp_retries1, 0, 0)) {
+ /* Black hole detection */
+ tcp_mtu_probing(icsk, sk); /* 如果开启了tcp_mtu_probing,则执行PMTU */
+ /* 更新路由缓存,用以避免由于路由选路变化带来的问题 */
+ dst_negative_advice(sk);
+ }
+
+ retry_until = sysctl_tcp_retries2;
+ if (sock_flag(sk, SOCK_DEAD)) {
+ /* 表示是没有应用在使用的一个孤立的socket */
+ const int alive = icsk->icsk_rto < TCP_RTO_MAX;
+
+ retry_until = tcp_orphan_retries(sk, alive);
+ do_reset = alive ||
+ !retransmits_timed_out(sk, retry_until, 0, 0);
+
+ if (tcp_out_of_resources(sk, do_reset))
+ return 1;
+ }
+ }
+
+ /* 一般来说,如果没有孤儿socket,那么一般重传次数是tcp_retries2 */
+ if (retransmits_timed_out(sk, retry_until,
+ syn_set ? 0 : icsk->icsk_user_timeout, syn_set)) {
+ /* Has it gone just too far? */
+ tcp_write_err(sk); /* 最终会调用tcp_done关闭TCP流 */
+ return 1;
+ }
+ return 0;
+}
+{% endhighlight %}
+
+在如上的函数中,两次超时时间的计算都是通过 retransmits_timed_out() 函数计算的,那么也就是该函数实际会判断是否已经超时。
+
+{% highlight c %}
+#define tcp_time_stamp ((__u32)(jiffies))
+
+static bool retransmits_timed_out(struct sock *sk,
+ unsigned int boundary,
+ unsigned int timeout,
+ bool syn_set)
+{
+ unsigned int linear_backoff_thresh, start_ts;
+ /* 如果是在三次握手阶段,syn_set为真 */
+ unsigned int rto_base = syn_set ? TCP_TIMEOUT_INIT : TCP_RTO_MIN;
+
+ /* 如果设置的重传次数为0,那么就直接退出 */
+ if (!inet_csk(sk)->icsk_retransmits)
+ return false;
+
+ /* 获取数据包第一次发送的时间,在tcp_retransmit_skb()中设置 */
+ start_ts = tcp_sk(sk)->retrans_stamp;
+ if (unlikely(!start_ts))
+ start_ts = tcp_skb_timestamp(tcp_write_queue_head(sk));
+
+ /* 如果用户态没有指定timeout,则自动计算一个出来 */
+ if (likely(timeout == 0)) {
+ /* 计算一下以rto_base为第一次重传间隔,重传boundary次所需要的时间 */
+ linear_backoff_thresh = ilog2(TCP_RTO_MAX/rto_base);
+
+ if (boundary <= linear_backoff_thresh)
+ timeout = ((2 << boundary) - 1) * rto_base;
+ else
+ timeout = ((2 << linear_backoff_thresh) - 1) * rto_base +
+ (boundary - linear_backoff_thresh) * TCP_RTO_MAX;
+ }
+
+ /* 如果数据包第一次发送的时间距离现在的时间间隔,超过了timeout值,则认为重传超于阈值了 */
+ return (tcp_time_stamp - start_ts) >= timeout;
+}
+{% endhighlight %}
+
+从以上的代码分析可以看到,如果用户没有设置超时时间,那么真正起到限制重传次数的并不是真正的重传次数,而是与此相关的一个超时时间。
+
+如上,是以 tcp_retries1 或 tcp_retries2 为 boundary,以 rto_base (如 TCP_RTO_MIN=200ms) 为初始的 RTO,计算出来一个 timeout 值,当重传间隔超过这个 timeout 后,则认为超过了阈值。
+
+如文档中所说的,当 tcp_retries2=15 时,那么计算得到的 timeout 是 924600ms,这个值是如何计算到的?简单来说,还是在 retransmits_timed_out() 函数计算完成,计算过程如下:
+
+{% highlight text %}
+----- 首先计算thresh
+linear_backoff_thresh = ilog2(TCP_RTO_MAX/rto_base) = ilog2((120*HZ)/(HZ/5)) = ilog2(600) = 9
+
+if (boundary <= linear_backoff_thresh)
+ timeout = ((2 << boundary) - 1) * rto_base;
+else
+ timeout = ((2 << linear_backoff_thresh) - 1) * rto_base +
+ (boundary - linear_backoff_thresh) * TCP_RTO_MAX;
+
+----- 此时选择的是第二个分支
+timeout = (2<<9 - 1) * (HZ/5) + (15 - 9) * 120 * HZ = 924.6s
+{% endhighlight %}
+
+如果用户没有指定超时时间,那么默认就是 924.6s,而对于具体重试了多少次,是与 RTT 相关的。
+
+1. 如果 RTT 比较小,那么 RTO 初始值就约等于下限 200ms,这时表现出来的现象基本就是刚好重传了 15 次。
+
+2. 如果 RTT 较大,比如 RTO 初始值计算得到的是 1000ms,那么根本不需要重传 15 次,重传总间隔就会超过 924.6s 。
+
+对于上述的问题,基本也就有结论了:
+
+1. 重试超过了 tcp_retries1 之后,怀疑路由有问题,直接更新路由缓存。
+
+2. 通过 tcp_retries 计算出来的是时间,而具体重传了多少次是与实际的 RTT 相关的。
+
+3. effective timeout 实际就是 retransmits_timed_out() 函数计算得到的 timeout 值。所谓的 lower / upper bound 是指超时时间的范围,如果是 15 那么实际的重传范围是 [924.6s, 1044.6s) 。
+
+
+# tcp_orphan_retries
+
+首先介绍一下什么是 orphan socket,简单来说就是该 socket 不与任何一个文件描述符相关联。例如,当应用主动调用 close() 关闭一个链接时,此时该 socket 就成为了 orphan,但是该 sock 仍然会保留一段时间,直到最后根据 TCP 协议结束。
+
+
+![fourway]{: .pull-center}
+
+如上是 TCP 关闭链接时的握手过程。
+
+主动关闭的一方发出 FIN,同时进入 FIN_WAIT1 状态,被动关闭的一方响应 ACK,从而使主动关闭的一方迁移至 FIN_WAIT2 状态,接着被动关闭的一方同样会发出 FIN,主动关闭的一方响应 ACK,同时链接的状态迁移至 TIME_WAIT 。
+
+那么这与 tcp_orphan_retries 参数有什么关系?
+
+正常来说,服务器间的 ACK 确认是非常快的,通常不会有 FIN_WAIT1 状态存在,如果被动关闭的一段很长时间没有响应,此时的 TCP 协议会如何处理呢。
+
+实际上这个参数决定了 FIN_WAIT1 状态的持续时间,其计算方式与 tcp_retries1 的相同。
+
+内核中还有一个容易混淆的参数 net.ipv4.tcp_fin_timeout,它实际上它控制的是 FIN_WAIT2 的超时时间,其定义如下:
+
+{% highlight text %}
+tcp_fin_timeout - INTEGER
+ The length of time an orphaned (no longer referenced by any
+ application) connection will remain in the FIN_WAIT_2 state
+ before it is aborted at the local end. While a perfectly
+ valid "receive only" state for an un-orphaned connection, an
+ orphaned connection in FIN_WAIT_2 state could otherwise wait
+ forever for the remote to close its end of the connection.
+ Cf. tcp_max_orphans
+ Default: 60 seconds
+{% endhighlight %}
+
+需要注意的是,在 tcp_orphan_retries 的定义中,如果要设置成一个比较小的值,该值应该至少大于0,如果是 0 那么实际等同于 8 ,对应的代码如下:
+
+{% highlight c %}
+/* Calculate maximal number or retries on an orphaned socket. */
+static int tcp_orphan_retries(struct sock *sk, int alive)
+{
+ int retries = sysctl_tcp_orphan_retries; /* May be zero. */
+
+ /* We know from an ICMP that something is wrong. */
+ if (sk->sk_err_soft && !alive)
+ retries = 0;
+
+ /* However, if socket sent something recently, select some safe
+ * number of retries. 8 corresponds to >100 seconds with minimal
+ * RTO of 200msec. */
+ if (retries == 0 && alive)
+ retries = 8;
+ return retries;
+}
+{% endhighlight %}
+
+可以得出结论,如果系统负载较重,有很多处于 FIN_WAIT1 的链接,那么可以通过降低 tcp_orphan_retries 来解决问题,具体设置多少视网络条件而定。
+
+另外,在遇到 DoS 攻击时,可以通过设置 tcp_max_orphans 减小。但是这和用来控制 TIME_WAIT 的最大值的 tcp_max_tw_buckets 参数一样,除非你遇到了 DoS 攻击,否则最好不要降低它。
+
+
+## 如何关闭一个链接
+
+如果要关闭一个 TCP 连接,那么需要知道相应的 ACK 和 SEQ ,然后才可以 RESET 连接。
+
+为了获取 ACK 和 SEQ 可以有主动与被动两种,分别有 tcpkill 以及 killcx,其中后者是 Perl 写的脚本。
+
+
+
+
+
+
+
+
+
+
+
+
+[fourway]: /images/linux/four-way-handshake.png
+
+
diff --git a/_drafts/2015-08-19-mysql-backup-strategy_init.md b/_drafts/2015-08-19-mysql-backup-strategy_init.md
new file mode 100644
index 0000000..994a182
--- /dev/null
+++ b/_drafts/2015-08-19-mysql-backup-strategy_init.md
@@ -0,0 +1,121 @@
+---
+title: MySQL 备份策略
+layout: post
+comments: true
+language: chinese
+category: [mysql,database]
+keywords: mysql,备份,mysqldump,mysqlbinlog,xtrabackup
+description: 为了保证数据安全,都会对硬件做高可用,防止出现单点故障,但是无论如何都无法取代备份,尤其对于数据库中所保存的数据而言。在此,介绍一下 MySQL 中常用的备份方法。
+---
+
+
+
+
+
+## 准备工作
+
+在刚接触一个环境时,首先需要确认一下几点的数据:
+
+1. 确定需要备份数据的大小,包括使用的存储引擎,例如 InnoDB、MyISAM、ARCHIVE 等;
+2. 如果要获取一致性读,可以使用什么样的锁策略,当然这也根上述的存储引擎相关;
+3. 备份时会花费多长时间;
+4. 是否有维护时间窗口,通常是没有对外提供 7*24 服务的业务。
+
+#### 确定备份大小
+
+可以通过如下方式查看当前某个库的大小。
+
+{% highlight text %}
+mysql> SELECT ROUND(SUM(data_length+index_length)/1024/1024) AS total_mb,
+ ROUND(SUM(data_length)/1024/1024) AS data_mb,
+ ROUND(SUM(index_length)/1024/1024) AS index_mb
+ FROM information_schema.tables
+ WHERE table_schema NOT IN
+ ('information_schema', 'performance_schema', 'mysql', 'sys');
+{% endhighlight %}
+
+
+
+这里计算的大小会与实际导出的文件有些许区别,不过相差不大。
+
+#### 使用锁策略
+
+锁的策略决定了备份时是否会影响到在线业务的读写,例如对于 mysqldump 命令可以通过 \-\-lock-tables 参数开启表锁,也就是 ```LOCK TABLES``` 命令;显然,这会影响到线上业务,不过对于 MyISAM 存储引擎来说,如果要获取到一致性的读,这是必须的。
+
+对于 mysqldump 命令,同时也提供了一个 \-\-single-transaction 参数在备份时开启一个事务,当然这需要存储引擎支持多版本事务,例如 InnoDB ,当使用该选项时,则会自动关闭 \-\-lock-tables 。
+
+可以通过如下命令查看每个数据库包含的存储引擎。
+
+{% highlight text %}
+mysql> SELECT table_schema,engine,count(1) AS tables
+ FROM information_schema.tables
+ WHERE table_schema NOT IN
+ ('information_schema', 'performance_schema', 'mysql', 'sys')
+ GROUP BY table_schema, engine
+ ORDER BY 3 DESC;
+{% endhighlight %}
+
+#### 备份时间
+
+这个实际上比较难测量,包括使用的内存、备份的并发等等,所以在此就不过多介绍了。
+
+#### 总结
+
+可以将当前库的大小、存储引擎信息通过如下 SQL 一次查看。
+
+{% highlight text %}
+mysql> SELECT table_schema, engine,
+ ROUND(SUM(data_length+index_length)/1024/1024) AS total_mb,
+ ROUND(SUM(data_length)/1024/1024) AS data_mb,
+ ROUND(SUM(index_length)/1024/1024) AS index_mb,
+ count(1) AS tables
+ FROM information_schema.tables
+ WHERE table_schema NOT IN
+ ('information_schema', 'performance_schema', 'mysql', 'sys')
+ GROUP BY table_schema, engine
+ ORDER BY 3 DESC;
+{% endhighlight %}
+
+收集到信息之后,接下来就是要决定如何备份、什么时候执行备份;在备份时需要检查是否备份成功,备份的大小等信息。
+
+{% highlight text %}
+----- 执行备份,并计算备份的时间
+$ time mysqldump -uroot -p --all-databases > backup.sql
+----- 查看备份的返回值,确认返回0,否则备份时有异常
+$ echo $?
+----- 查看备份的大小
+$ ls -lh backup.sql
+{% endhighlight %}
+
+当然,备份完成之后,可以通过 rsync 同步一份到其它的服务器上,防止数据损坏。
+
+
+
+
+Point In Time Recovery, PITR
+
+
+
+
+
+
+
+
+
+
+
+## 参考
+
+[Effective MySQL: Backup and Recovery](http://apprize.info/php/effective/index.html)
+
+
+
+{% highlight text %}
+{% endhighlight %}
diff --git a/_drafts/2015-08-24-mysql-high-availability_init.md b/_drafts/2015-08-24-mysql-high-availability_init.md
new file mode 100644
index 0000000..19feefc
--- /dev/null
+++ b/_drafts/2015-08-24-mysql-high-availability_init.md
@@ -0,0 +1,213 @@
+---
+Date: October 19, 2013
+title: MySQL 高可用
+layout: post
+comments: true
+language: chinese
+category: [mysql,database]
+---
+
+在此会介绍 MySQL 的高可用解决方案。
+
+
+
+
+实际上高可用需要从软件和硬件进行保证,目前对于 MySQL 来说,常见的高可用解决方案可以参考如下内容:
+
+* 硬件复用。基本都是两套系统,如多路电源、双网卡、RAID 卡等,从而避免单点故障 (Single Point Of Failure, SPOF)。
+
+* 共享存储。RAID 可以避免单磁盘故障,但是如过整机故障仍有问题,此时可以使用一个备机,同时使用 SAN/NAS 搭建共享存储。这样单机故障之后,直接利用共享存储上的数据,在备机上启动进程即可。
+
+* Distributed Replicated Block Device, DRBD。于共享存储相似,也是为了解决存储的故障,不过这是内核提供的一个文件复制模块。
+
+* 主备复制。也就是本文讨论的内容。
+
+* Network DataBase, NDB。可以支持多个示例写入,从而当一个服务器宕机之后,另外的机器仍可以提供服务。不过,这种方式只适合写多读少的场景。
+
+* Galera。MySQL 的多主在发生网络分区后,可能会导致不一致产生,而 Galera 可以保证在副本中的多数派写入成功之后才提交,从而保证数据一致性。
+
+* Percona XtraDB Cluster, PXC。这个是对 Galera 的优化,原理基本相似,还没有仔细研究。
+
+接下来我们看看是如何通过复制来获取高可用的。
+
+{% highlight text %}
+=== Master Slave ===
++-----+ +-----+
+|-----| | |
+|--M--| =>=> | S |
+|-----| | |
++-----+ +-----+
+
+=== Master Multi-Slave ===
+ +-----+
+ | |
+ +=>| S |
+ | | |
+ | +-----+
+ |
++-----+ | +-----+
+|-----| | | |
+|--M--|=>=+=>| S |
+|-----| | | |
++-----+ | +-----+
+ |
+ | +-----+
+ | | |
+ +=>| S |
+ | |
+ +-----+
+
+=== Master Master ===
++-----+ +-----+
+|-----| |-----|
+|--M--| <==> |--M--|
+|-----| |-----|
++-----+ +-----+
+
+=== Master Slave Slave ===
+ +-----+
+ | |
+ +=>| S |
+ | | |
+ | +-----+
+ |
++-----+ +-----+ | +-----+
+|-----| | | | | |
+|--M--| =>=> | S | =+=>| S |
+|-----| | | | | |
++-----+ +-----+ | +-----+
+
+ | +-----+
+ | | |
+ +=>| S |
+ | |
+ +-----+
+{% endhighlight %}
+
+# 参考
+
+当前 MySQL 高可用解决方案的很不错介绍,可以参考 [MySQL high availability -- Top 7 solutions to improve MySQL uptime](https://bobcares.com/blog/mysql-high-availability-top-7-solutions/),或者可以参考 [本地保存的版本](/reference/mysql/Top 7 solutions to improve MySQL uptime.mht) 。
+
+
+
+
+MySQL的各种高可用方案,大多是基于以下几种基础来部署的:
+
+基于主从复制;
+
+基于Galera协议;
+
+基于NDB引擎;
+
+基于中间件/proxy;
+
+基于共享存储;
+
+基于主机高可用;
+
+在这些可选项中,最常见的就是基于主从复制的方案,其次是基于Galera的方案,我们重点说说这两种方案。其余几种方案在生产上用的并不多,我们只简单说下。
+
+
+
+
+# 基于主从复制
+
+双节点主从 + keepalived/heartbeat
+
+一般来说,中小型规模的时候,采用这种架构是最省事的。
+
+两个节点可以采用简单的一主一从模式,或者双主模式,并且放置于同一个VLAN中,在master节点发生故障后,利用keepalived/heartbeat的高可用机制实现快速切换到slave节点。
+
+在这个方案里,有几个需要注意的地方:
+
+采用keepalived作为高可用方案时,两个节点最好都设置成BACKUP模式,避免因为意外情况下(比如脑裂)相互抢占导致往两个节点写入相同数据而引发冲突;
+
+把两个节点的auto_increment_increment(自增起始值)和auto_increment_offset(自增步长)设成不同值。其目的是为了避免master节点意外宕机时,可能会有部分binlog未能及时复制到slave上被应用,从而会导致slave新写入数据的自增值和原先master上冲突了,因此一开始就使其错开;当然了,如果有合适的容错机制能解决主从自增ID冲突的话,也可以不这么做;
+
+slave节点服务器配置不要太差,否则更容易导致复制延迟。作为热备节点的slave服务器,硬件配置不能低于master节点;
+
+如果对延迟问题很敏感的话,可考虑使用MariaDB分支版本,或者直接上线MySQL 5.7最新版本,利用多线程复制的方式可以很大程度降低复制延迟;
+
+对复制延迟特别敏感的另一个备选方案,是采用semi sync replication(就是所谓的半同步复制)或者后面会提到的PXC方案,基本上无延迟,不过事务并发性能会有不小程度的损失,需要综合评估再决定;
+
+keepalived的检测机制需要适当完善,不能仅仅只是检查mysqld进程是否存活,或者MySQL服务端口是否可通,还应该进一步做数据写入或者运算的探测,判断响应时间,如果超过设定的阈值,就可以启动切换机制;
+
+keepalived最终确定进行切换时,还需要判断slave的延迟程度。需要事先定好规则,以便决定在延迟情况下,采取直接切换或等待何种策略。直接切换可能因为复制延迟有些数据无法查询到而重复写入;
+
+keepalived或heartbeat自身都无法解决脑裂的问题,因此在进行服务异常判断时,可以调整判断脚本,通过对第三方节点补充检测来决定是否进行切换,可降低脑裂问题产生的风险。
+
+双节点主从+keepalived/heartbeat方案架构示意图见下:
+MySQL双节点高可用架构
+
+图解:MySQL双节点(单向/双向主从复制),采用keepalived实现高可用架构。
+
+多节点主从+MHA/MMM
+
+多节点主从,可以采用一主多从,或者双主多从的模式。
+
+这种模式下,可以采用MHA或MMM来管理整个集群,目前MHA应用的最多,优先推荐MHA,最新的MHA也已支持MySQL 5.6的GTID模式了,是个好消息。
+
+MHA的优势很明显:
+
+开源,用Perl开发,代码结构清晰,二次开发容易;
+
+方案成熟,故障切换时,MHA会做到较严格的判断,尽量减少数据丢失,保证数据一致性;
+
+提供一个通用框架,可根据自己的情况做自定义开发,尤其是判断和切换操作步骤;
+
+支持binlog server,可提高binlog传送效率,进一步减少数据丢失风险。
+
+不过MHA也有些限制:
+
+需要在各个节点间打通ssh信任,这对某些公司安全制度来说是个挑战,因为如果某个节点被黑客攻破的话,其他节点也会跟着遭殃;
+
+自带提供的脚本还需要进一步补充完善,当然了,一般的使用还是够用的。
+
+多节点主从+etcd/zookeeper
+
+在大规模节点环境下,采用keepalived或者MHA作为MySQL的高可用管理还是有些复杂或麻烦。
+
+首先,这么多节点如果没有采用配置服务来管理,必然杂乱无章,线上切换时很容易误操作。
+
+在较大规模环境下,建议采用etcd/zookeeper管理集群,可实现快速检测切换,以及便捷的节点管理。
+
+
+
+## 搭建环境
+
+下图演示了从一个主服务器(master)把数据同步到从服务器(slave)的过程。
+
+![Simple Master Slave]({{ site.url }}/images/databases/mysql/ha_master_slave.jpg "Simple Master Slave"){: .pull-center}
+
+这是一个简单的主-从复制的例子,而主-主互相复制只是把上面的例子反过来再做一遍,所以我们以这个例子介绍原理。
+
+对于一个mysql服务器,一般有两个线程来负责复制和被复制,当开启复制之后。
+
+1. 作为主服务器Master,会把自己的每一次改动都记录到二进制日志 Binarylog 中。
+
+2. 从服务器会用 master 上的账号登陆到 master ,读取 master 的 Binarylog ,写入到自己的中继日志 Relaylog ,然后自己的 sql 线程会负责读取这个中继日志,并执行一遍。
+
+其中服务器以 [MySQL 简介](/blog/mysql-introduce.html) 中介绍的多实例为例,在配置文件中需要设置 log-bin=mysql-bin ,server-id 两个参数,第一个表示启用二进制日志。
+
+{% highlight text %}
+----- 在主建立帐户并授权slave,一般来说用其它帐号也可以
+mysql> grant replication slave on *.* to 'mysync'@'localhost' identified by '123456';
+
+----- 查看主服务器的状态,记住现在的File和Position
+mysql> show master status;
+
+----- 启动slave
+mysql> change master to master_host='localhost', master_user='mysync', master_port=3306,
+ -> master_password='123456', master_log_file='mysql-bin.000001', master_log_pos=313;
+
+----- 在导出数据时可以执行如下内容
+mysql> flush tables with read lock; // 锁定数据库然后将数据导出
+$ mysqldump --master-data -uroot -p some_db > some_db.sql // 将数据导出
+mysql> unlock tables; // 解锁数据库
+$ mysql -uroot -p some_db < some_db.sql // 重新导入
+{% endhighlight %}
+
+正常来说,只要开启了 bin-log ,show master status 都是有效的,show slave status 在备库中有效。
+
+{% highlight text %}
+{% endhighlight %}
diff --git a/_drafts/2015-08-25-mysql-backup-tools_init.md b/_drafts/2015-08-25-mysql-backup-tools_init.md
new file mode 100644
index 0000000..8e23c91
--- /dev/null
+++ b/_drafts/2015-08-25-mysql-backup-tools_init.md
@@ -0,0 +1,391 @@
+---
+title: MySQL 备份工具
+layout: post
+comments: true
+language: chinese
+category: [mysql,database]
+keywords: mysql,备份,mysqldump,mysqlbinlog,xtrabackup
+description: 为了保证数据安全,都会对硬件做高可用,防止出现单点故障,但是无论如何都无法取代备份,尤其对于数据库中所保存的数据而言。在此,介绍一下 MySQL 中常用的备份方法。
+---
+
+为了保证数据安全,都会对硬件做高可用,防止出现单点故障,但是无论如何都无法取代备份,尤其对于数据库中所保存的数据而言。
+
+在此,介绍一下 MySQL 中常用的备份方法。
+
+
+
+## 简介
+
+MySQL 自带了 mysqldump、mysqlbinlog 两个备份工具,percona 又提供了强大的 XtraBackup,加上开源的 mydumper,还有基于主从同步的延迟备份、从库冷备等方式,以及基于文件系统快照的备份,其实选择已经多到眼花缭乱。
+
+备份本身是为了能在出现故障时,可以迅速、准确恢复到原有状态,如果同时可以简单实用,那就最好不过了。接下来,我们看看这几种常用的备份工具。
+
+## mysqldump mydumper
+
+mysqldump 是最简单的逻辑备份方式,在备份 myisam 表的时候,如果要得到一致的数据,就需要锁表。
+
+备份 innodb 表时,加上 \-\-master-data=1 \-\-single-transaction 选项,在事务开始记录下 binlog pos 点,然后利用 mvcc 来获取一致的数据,由于是一个长事务,在写入和更新量很大的数据库上,将产生非常多的 undo,显著影响性能,所以要慎用。
+
+这种方式可以针对单表备份,可以导出表结构。
+
+mydumper 是 mysqldump 的开源加强版,支持压缩,支持并行备份和恢复,速度比要快很多,但是由于是逻辑备份,仍不是很快。
+
+
+
+## XtraBackup
+
+这是 percona 开发的一个物理备份工具,可以在线对 InnoDB/XtraDB 引擎的表进行物理备份,相比于 mysqldump 来说,效率很不错。
+
+XtraBackup 包含了两个主要的工具 xtrabackup、innobackupex,其中 xtrabackup 只能备份 InnoDB 和 XtraDB 两种数据表;innobackupex 则封装了 xtrabackup 同时可以备份 MyISAM 数据表。
+
+如果通过源码进行编译,需要 boost 库才可以,为了简单起见,可以直接安装二进制文件。
+
+{% highlight text %}
+# yum install libev
+# rpm -ivh percona-xtrabackup-xxx.rpm
+{% endhighlight %}
+
+如果是源码安装,可以直接从 [Github - Percona XtraBackup](https://github.com/percona/percona-xtrabackup) 下载。
+
+{% highlight text %}
+innobackupex
+xbcrypt
+xbstream
+xtrabackup
+{% endhighlight %}
+
+实际上,在 2.3 版本之前,innobackupex 是通过 perl 实现的,而在此之后则采用 C 进行了重写;为了保持向前兼容,该文件实际上是指向 xtrabackup 的一个符号连接。
+
+### apply-log
+
+一般情况下,在备份完成后,数据尚且不能用于恢复操作,因为备份的数据中可能会包含尚未提交的事务或已经提交但尚未同步至数据文件中的事务。也就是说,此时数据文件仍处理不一致状态;需要回滚未提交的事务及同步已经提交的事务至数据文件,使得数据文件处于一致性状态。
+
+innobakupex 命令通过的 \-\-apply-log 选项可用于实现上述功能。
+
+## 备份
+
+如 [MySQL 复制](/blog/mysql-replication.html) 中所示,假设搭建了一个 Master 服务器。
+
+### 全量备份
+
+可以通过如下步骤进行全量备份。
+
+{% highlight text %}
+----- 全量备份到/tmp/percon目录下
+$ innobackupex --defaults-file=/tmp/my-master.cnf --user=root \
+ --socket=/tmp/mysql-master.sock /tmp/percon
+... ...
+MySQL binlog position: filename 'mysql-bin.000001', position '1687589', GTID of the last change '0-1-5439'
+161205 20:56:49 [00] Writing backup-my.cnf
+161205 20:56:49 [00] ...done
+161205 20:56:49 [00] Writing xtrabackup_info
+161205 20:56:49 [00] ...done
+xtrabackup: Transaction log of lsn (4397451) to (4397451) was copied.
+161205 20:56:49 completed OK!
+{% endhighlight %}
+
+当结尾为 __completed OK!__ 时,说明备份成功。备份完成之后,在目录下会有几个比较重要的元数据文件,简单介绍如下。
+
+{% highlight text %}
+$ cat xtrabackup_checkpoints
+backup_type = full-backuped # 备份类型
+from_lsn = 0
+to_lsn = 4397451
+last_lsn = 4397451 # LSN号
+compact = 0
+recover_binlog_info = 0
+
+$ cat xtrabackup_binlog_info # 备份时binlog文件及其位置
+mysql-bin.000001 1687589 0-1-5439
+
+$ cat xtrabackup_binlog_pos_innodb # 同上,不包括GTID信息
+mysql-bin.000001 1687589
+{% endhighlight %}
+
+在使用 innobackupex 进行备份时,可以使用 \-\-no-timestamp 选项来阻止命令自动创建一个以时间命名的目录;此时,该命令将会创建一个 BACKUP-DIR 目录来存储备份数据。
+
+
+### 全量恢复
+
+假设将其中的一个数据库删除掉,然后可以通过上述的备份恢复该数据库。
+
+{% highlight text %}
+----- 关闭数据库
+$ mysqladmin -uroot -S /tmp/mysql-master.sock shutdown
+
+----- 备份源数据库,并新建相同目录
+$ mv /tmp/mysql-master /tmp/mysql-master-bak
+$ mkdir /tmp/mysql-master
+
+----- 准备备份的全量数据
+$ innobackupex --apply-log /tmp/percon/2015-08-23_20-55-32/
+... ...
+xtrabackup: starting shutdown with innodb_fast_shutdown = 1
+InnoDB: FTS optimize thread exiting.
+InnoDB: Starting shutdown...
+InnoDB: Shutdown completed; log sequence number 4406415
+161205 21:27:15 completed OK!
+
+----- 恢复全量数据
+$ innobackupex --defaults-file=/tmp/my-master.cnf --copy-back --rsync /tmp/percon/2015-08-23_20-55-32/
+... ...
+161205 21:34:36 completed OK!
+
+----- 启动服务器
+$ /opt/mariadb/bin/mysqld --defaults-file=/tmp/my-master.cnf --basedir=/opt/mariadb \
+ --datadir=/tmp/mysql-master
+{% endhighlight %}
+
+### 增量备份
+
+注意,增量备份需要先有一个全量的备份,假设仍使用上述的备份。
+
+{% highlight text %}
+mysql> INSERT INTO test.foobar VALUES(5, "Hobart"), (6, "Raymond");
+Query OK, 2 rows affected (0.01 sec)
+Records: 2 Duplicates: 0 Warnings: 0
+
+----- 执行增量备份
+$ innobackupex --defaults-file=/tmp/my-master.cnf --user=root \
+ --socket=/tmp/mysql-master.sock --incremental /tmp/percon/ \
+ --incremental-basedir=/tmp/percon/2015-08-23_20-55-23/ --parallel=2
+... ...
+xtrabackup: Transaction log of lsn (4408143) to (4408143) was copied.
+161205 21:49:34 completed OK!
+
+
+mysql> INSERT INTO test.foobar VALUES(7, "Jeremy"), (8, "Philip");
+Query OK, 2 rows affected (0.01 sec)
+Records: 2 Duplicates: 0 Warnings: 0
+
+$ innobackupex --defaults-file=/tmp/my-master.cnf --user=root \
+ --socket=/tmp/mysql-master.sock --incremental /tmp/percon/ \
+ --incremental-basedir=/tmp/percon/2015-08-23_21-48-23/ --parallel=2
+... ...
+xtrabackup: Transaction log of lsn (4408743) to (4408743) was copied.
+161205 21:59:20 completed OK!
+{% endhighlight %}
+
+### 增量备份恢复
+
+对于如上的增量备份,恢复时需要执行如下的 3 个步骤。
+
+1. 准备完全备份;
+
+2. 将增量备份恢复到完全备份,开始恢复的增量备份要添加 \-\-redo-only 参数,到最后一次增量备份要去掉该参数;
+
+3. 对整体的完全备份进行恢复,回滚未提交的数据。
+
+{% highlight text %}
+mysql> DROP DATABASE TEST;
+
+----- 将增量1应用到完全备份
+$ innobackupex --apply-log --redo-only /tmp/percon/2015-08-23_20-55-23/ \
+ --incremental-dir=/tmp/percon/2015-08-23_21-48-23/
+
+----- 将增量2应用到完全备份
+$ innobackupex --apply-log /tmp/percon/2015-08-23_21-48-23 --incremental-dir=/tmp/pecon/2015-08-23_21-58-40/
+
+----- 把所有合在一起的完全备份整体进行一次apply操作,回滚未提交的数据
+$ innobackupex --apply-log /tmp/percon/2015-08-23_20-55-23/
+
+----- 恢复数据
+$ innobackupex --defaults-file=/tmp/my-master.cnf --copy-back --rsync /tmp/percon/2015-08-23_20-55-23/
+{% endhighlight %}
+
+另外,可以对数据进行压缩,在此暂不讨论。
+
+
+## 执行流程
+
+如下是 xtrabackup 执行的详细流程。
+
+![mysql percona xtrabackup procedure]({{ site.url }}/images/databases/mysql/pxb-backup-procedure.png){: .pull-center}
+
+
+
+
+## mysqlbinlog
+
+上述备份方式,都只能把数据库恢复到备份时的时间点:A) mysqldump、mydumper、snapshot 是备份开始的时间点;B) XtraBackup 是备份结束的时间点。
+
+如果要实现 point in time 恢复,还必须备份 binlog;开启 binlog 非常简单,可以通过修改如下配置文件打开。
+
+{% highlight text %}
+$ vi /etc/my.cnf
+binlog_format = mixed
+log-bin = mysql-bin
+{% endhighlight %}
+
+其中 mysql 5.6 提供了远程备份 binlog 的能力,我么可以直接通过如下命令远程备份。
+
+{% highlight text %}
+$ mysqlbinlog --raw --read-from-remote-server --stop-never
+{% endhighlight %}
+
+该命令会伪装成 mysql 从库,从远程获取 binlog 然后进行转储,不过这样状态监控需要单独部署;当然,还可以通过 blackhole 来备份全量的 binlog 。
+
+## 常用解析
+
+通过 mysqlbinlog 工具可以将 binlog 或者 relay-log 文件解析成文本文件,两者的格式相同。
+
+{% highlight text %}
+$ mysqlbinlog binlog.0000003
+{% endhighlight %}
+
+如上命令将会解析 binlog ,并输出该文件中的所有语句及其相关信息,例如每个语句花费的时间、客户发出的线程ID、发出线程时的时间戳等等。
+
+也可以通过 mysql 客户端查看 binlog 相关信息。
+
+{% highlight text %}
+mysql> SHOW BINARY LOGS; # 查看binlog日志
++-----------------+-----------+
+| Log_name | File_size |
++-----------------+-----------+
+| mysql-bin.000001| 409 |
+| mysql-bin.000002| 346 |
++-----------------+-----------+
+2 rows in set (0.00 sec)
+
+mysql> SHOW BINLOG EVENTS; # 查看执行的SQL,默认是1
+mysql> SHOW BINLOG EVENTS IN 'mysql-bin.000002'; # 查看指定binlog文件的内容
+mysql> SHOW BINLOG EVENTS FROM 213; # 指定位置binlog的内容
+{% endhighlight %}
+
+接下来看看如何通过 mysqlbinlog 方式提取 binlog 日志。
+
+{% highlight text %}
+----- 提取整个binlog日志,压缩,执行
+$ mysqlbinlog mysql-bin.000001
+$ mysqlbinlog mysql-bin.000001 | gzip > foobar.sql.gz
+$ mysqlbinlog mysql-bin.000001 | mysql -uroot -p
+
+----- 提取指定位置的binlog日志
+$ mysqlbinlog --start-position="120" --stop-position="332" mysql-bin.000001
+
+----- 指定数据库、字符集、开始时间、结束时间的binlog并输出到日志文件
+$ mysqlbinlog --database=test --set-charset=utf8 \
+ --start-datetime="2015-08-23 21:15:23" --stop-datetime="2015-08-23 22:15:23" \
+ --result-file=foobar.sql mysql-bin.000002 mysql-bin.000003
+
+----- 远程提取日志,指定结束时间
+# mysqlbinlog -uroot -p -h127.1 -P3306 --stop-datetime="2015-08-23 22:30:23" \
+ --read-from-remote-server mysql-bin.000033 | less
+
+----- 远程提取使用row格式的binlog日志并输出到本地文件
+# mysqlbinlog -uroot -p -P3306 -h127.1 --read-from-remote-server -vv \
+ mysql-bin.000005 > foobar.sql
+
+常用参数:
+ -p, --password[=name] # 密码
+ -P, --port[=num] # 端口号
+ -u, --user=name # 登陆用户名
+ -h, --host=name # 主机地址
+ -S, --socket=name # 套接字文件
+ -d, --database=name # 只列出该数据库的条目(只适用本地日志)
+ --protocol=name # 连接协议
+ --server-id[=num] # 仅提取指定id的binlog日志
+
+ --set-charset=name # 添加SET NAMES character_set到输出
+ -r, --result-file=name # 输出指向给定文件
+ -s, --short-form # 只显示包含的语句
+
+ --start-datetime=name # 等于或大于该值的事件
+ --stop-datetime=name # 小于或等于该值的事件
+
+ -j, --start-position[=num] # 从指定位置开始读取事件
+ --stop-position[=num] # 到该位置停止
+ -o, --offset[=num] # 跳过前num个条目
+
+ -f, --force-read
+ 无法识别二进制事件,打印警告并忽略该事件继续;否则停止
+ -H, --hexdump
+ 在注释中显示日志的十六进制转储,可以用于调试
+{% endhighlight %}
+
+
+mysqlbinlog 可以基于 server_id,以及基于数据库级别提取日志,不支持表级别。
+
+
+
+
+## 参考
+
+关于 XtraBackup 可以直接参考官方网站 [Percona XtraBackup - Documentation](https://www.percona.com/doc/percona-xtrabackup/index.html)。
+
+
+{% highlight text %}
+{% endhighlight %}
diff --git a/_drafts/2015-09-05-mysql-join_init.md b/_drafts/2015-09-05-mysql-join_init.md
new file mode 100644
index 0000000..10d36e7
--- /dev/null
+++ b/_drafts/2015-09-05-mysql-join_init.md
@@ -0,0 +1,1136 @@
+---
+title: MySQL 笛卡尔积
+layout: post
+comments: true
+language: chinese
+category: [mysql,database]
+keywords: mysql,笛卡尔积,join
+description: 笛卡尔积实际上就是等值连接操作,MySQL 最初采用的是 Nested-Loop Join,也就是简单的嵌套循环查询,不过针对这种方式,进行了很多优化,在此就看看与 JOIN 相关的一些操作。
+---
+
+笛卡尔积实际上就是等值连接操作,MySQL 最初采用的是 Nested-Loop Join,也就是简单的嵌套循环查询,不过针对这种方式,进行了很多优化,在此就看看与 JOIN 相关的一些操作。
+
+
+
+## 简介
+
+关系数据库中的查询中,JOIN 是将两个数据集合按照某个条件进行合并形成新的数据集合的操作,其理论基础是关系代数,由一个迪卡尔积运算和一个选择运算构成。
+
+连接类型主要有两大类:内连接 (INNER JOIN, 也称为自然连接) 和外连接 (OUTER JOIN);其中外连接又包括左外连接 (LEFT OUTER JOIN)、右外连接 (RIGHT OUTER JOIN) 和全外连接 (FULL OUTER JOIN),不过 MySQL 不支持全外连接。
+
+另外,连接条件有三种:自然连接(NATURAL JOIN)、条件连接(ON 谓词条件)和指定属性的连接(USING 属性)。自然连接就是以两个集合的公共属性作为条件进行等值连接,也就是对于 A NATURAL JOIN B 来说,等价于 A JOIN B USING(A1, A2),其中 A1、A2 是 A 和 B 的所有公共属性。还有一种连接类型叫交叉连接 (CROSS JOIN),它等价于无连接条件的 NATURAL INNER JOIN。
+
+![mysql joins]({{ site.url }}/images/databases/mysql/joins.svg "mysql joins"){: .pull-center width="90%" }
+
+INNER JOIN 又称为等值连接,实际是笛卡尔积;LEFT JOIN 和 RIGHT JOIN 在概念上基本相同,只是 LEFT JOIN 会显示左侧的所有数据,如果存在不符合条件的记录则右侧的为 NULL ,通过这一特征可以确定右数据表中缺少了那些数据行 (注意,此时要求判断右行本身为 NOT NULL)。
+
+在 MySQL 中没有 FULL JOIN 的概念,但是可以将 LEFT/RIGHT 的结果做 UNION 操作;另外,在 MySQL 中,INNER JOIN、CROSS JOIN、JOIN 的操作相同。
+
+
+### 示例
+
+假设我们有两张表,Table A 是左边的表,Product;Table B 是右边的表,Product Details;通过如下命令创建示例表:
+
+{% highlight text %}
+CREATE TABLE tablea (
+ id int(10) unsigned NOT NULL auto_increment,
+ amount int(10) unsigned default NULL,
+ PRIMARY KEY (id)
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
+CREATE TABLE tableb (
+ id int(10) unsigned NOT NULL,
+ weight int(10) unsigned default NULL,
+ exist int(10) unsigned default NULL,
+ PRIMARY KEY (id)
+) ENGINE=InnoDB DEFAULT CHARSET=latin1;
+
+INSERT INTO tablea (id,amount) VALUES (1,100),(2,200),(3,300),(4,400);
+INSERT INTO tableb (id,weight,exist) VALUES (2,22,0),(4,44,1),(5,55,0),(6,66,1);
+
+ id amount id weight exist
+ -- ------ -- ------ -----
+ 1 100 2 22 0
+ 2 200 4 44 1
+ 3 300 5 55 0
+ 4 400 6 66 1
+{% endhighlight %}
+
+INNER JOIN 、CROSS JOIN、JOIN 以及 ',' 功能相似,需要注意的是 ',' 不能使用 ON 和 USING 子句。下面是简单的示例:
+
+{% highlight text %}
+----- 其中tablea为内循环,tableb为外循环
+mysql> SELECT * FROM tablea INNER JOIN tableb;
++----+--------+----+--------+-------+
+| id | amount | id | weight | exist |
++----+--------+----+--------+-------+
+| 1 | 100 | 2 | 22 | 0 |
+| 2 | 200 | 2 | 22 | 0 |
+| 3 | 300 | 2 | 22 | 0 |
+| 4 | 400 | 2 | 22 | 0 |
+| 1 | 100 | 4 | 44 | 1 |
+| 2 | 200 | 4 | 44 | 1 |
+| 3 | 300 | 4 | 44 | 1 |
+| 4 | 400 | 4 | 44 | 1 |
+| 1 | 100 | 5 | 55 | 0 |
+| 2 | 200 | 5 | 55 | 0 |
+| 3 | 300 | 5 | 55 | 0 |
+| 4 | 400 | 5 | 55 | 0 |
+| 1 | 100 | 6 | 66 | 1 |
+| 2 | 200 | 6 | 66 | 1 |
+| 3 | 300 | 6 | 66 | 1 |
+| 4 | 400 | 6 | 66 | 1 |
++----+--------+----+--------+-------+
+16 rows in set (0.00 sec)
+
+
+----- 等值JOIN,两者结果集相同,只是USING会自动合并一个id,而且使用USING时列名需相同
+mysql> SELECT * FROM tablea a INNER JOIN tableb b ON a.id = b.id;
++----+--------+----+--------+-------+
+| id | amount | id | weight | exist |
++----+--------+----+--------+-------+
+| 2 | 200 | 2 | 22 | 0 |
+| 4 | 400 | 4 | 44 | 1 |
++----+--------+----+--------+-------+
+2 rows in set (0.00 sec)
+
+MYSQL> SELECT * FROM tablea a INNER JOIN tableb b USING(id);
++----+--------+--------+-------+
+| id | amount | weight | exist |
++----+--------+--------+-------+
+| 2 | 200 | 22 | 0 |
+| 4 | 400 | 44 | 1 |
++----+--------+--------+-------+
+2 rows in set (0.00 sec)
+
+
+----- 首先确认tableb.id均为非NULL;然后利用左连接,查找a在b中不存在的id
+mysql> SELECT * FROM tableb where id is NULL;
+Empty set (0.00 sec)
+
+mysql> SELECT * FROM tablea a LEFT JOIN tableb b ON a.id = b.id where b.id is NULL;
++----+--------+------+--------+-------+
+| id | amount | id | weight | exist |
++----+--------+------+--------+-------+
+| 1 | 100 | NULL | NULL | NULL |
+| 3 | 300 | NULL | NULL | NULL |
++----+--------+------+--------+-------+
+2 rows in set (0.00 sec)
+
+
+----- 使用自连接
+mysql> SELECT a.id, b.id FROM tablea a INNER JOIN tablea b WHERE a.id > b.id;
++----+----+
+| id | id |
++----+----+
+| 2 | 1 |
+| 3 | 1 |
+| 3 | 2 |
+| 4 | 1 |
+| 4 | 2 |
+| 4 | 3 |
++----+----+
+6 rows in set (0.01 sec)
+
+----- 获取FULL JOIN
+mysql> SELECT * FROM tablea a LEFT JOIN tableb b ON a.id = b.id WHERE b.id IS NULL
+ UNION
+ SELECT * FROM tablea a RIGHT JOIN tableb b ON a.id = b.id WHERE a.id IS NULL;
++------+--------+------+--------+-------+
+| id | amount | id | weight | exist |
++------+--------+------+--------+-------+
+| 1 | 100 | NULL | NULL | NULL |
+| 3 | 300 | NULL | NULL | NULL |
+| NULL | NULL | 5 | 55 | 0 |
+| NULL | NULL | 6 | 66 | 1 |
++------+--------+------+--------+-------+
+4 rows in set (0.01 sec)
+{% endhighlight %}
+
+在 MySQL 中,尽量用 INNER JOIN 避免 LEFT JOIN 和 NULL。
+
+### ON vs. WHERE
+
+在 MySQL 中,ON 子句与 WHERE 子句是有所区别的,"A LEFT JOIN B ON 条件表达式" 中的 ON 用来决定如何从 B 表中检索数据行。如果 B 表中没有任何一行数据匹配 ON 的条件,将会额外生成一行所有列为 NULL 的数据。
+
+在匹配阶段 WHERE 子句的条件都不会被使用,仅在匹配阶段完成以后,WHERE 子句条件才会被使用,它将从匹配阶段产生的数据中检索过滤。
+
+可以通过如下的例子查看 ON 子句和 WHERE 子句的区别。
+
+{% highlight text %}
+mysql> SELECT * FROM tablea LEFT JOIN tableb ON (tablea.id = tableb.id) AND tableb.id=2;
++----+--------+------+--------+-------+
+| id | amount | id | weight | exist |
++----+--------+------+--------+-------+
+| 1 | 100 | NULL | NULL | NULL |
+| 2 | 200 | 2 | 22 | 0 |
+| 3 | 300 | NULL | NULL | NULL |
+| 4 | 400 | NULL | NULL | NULL |
++----+--------+------+--------+-------+
+4 rows in set (0.00 sec)
+
+mysql> SELECT * FROM tablea LEFT JOIN tableb ON (tablea.id = tableb.id) WHERE tableb.id=2;
++----+--------+----+--------+-------+
+| id | amount | id | weight | exist |
++----+--------+----+--------+-------+
+| 2 | 200 | 2 | 22 | 0 |
++----+--------+----+--------+-------+
+1 row in set (0.01 sec)
+{% endhighlight %}
+
+第一条查询使用 ON 条件决定了从 LEFT JOIN 的 tableb 表中检索条件符合 (两个条件都需要符合) 的所有数据行,不符合条件的右侧显示为NULL。
+
+第二条查询做了简单的 LEFT JOIN ,然后使用 WHERE 子句从 LEFT JOIN 的数据中过滤掉不符合条件的数据行。
+
+{% highlight text %}
+mysql> SELECT * FROM tablea LEFT JOIN tableb ON tablea.id = tableb.id AND tablea.amount=100;
++----+--------+------+--------+-------+
+| id | amount | id | weight | exist |
++----+--------+------+--------+-------+
+| 1 | 100 | NULL | NULL | NULL |
+| 2 | 200 | NULL | NULL | NULL |
+| 3 | 300 | NULL | NULL | NULL |
+| 4 | 400 | NULL | NULL | NULL |
++----+--------+------+--------+-------+
+4 rows in set (0.00 sec)
+{% endhighlight %}
+
+所有来自 tablea 表的数据行都被检索到了,但没有在 tableb 表中匹配到记录 (tablea.id = tableb.id AND product.amount=100 条件并没有匹配到任何数据,同上需要满足所有的条件)。
+
+
+
+## 实现
+
+数据库中 JOIN 操作的实现主要有三种:嵌套循环连接 (Nested Loop Join)、归并连接 (Merge Join) 和哈希连接 (Hash Join)。其中嵌套循环连接又视情况又有两种变形:块嵌套循环连接和索引嵌套循环连接。
+
+在 MySQL 中,采用的是 Nested Loop Join,也就是通过驱动表的结果集作为循环基础数据,然后一条一条的通过该结果集中的数据作为过滤条件到下一个表中查询数据,然后合并结果;如果还有第三个,则如此往复。
+
+因此,MySQL 可以用来做一些 "简单" 的分析查询,当数据量比较大时,即使支持 Hash Join 的传统 MPP 架构的关系型数据库也不太合适,这类需求应该交给更为专业的 Hadoop 集群来计算。
+
+> 数仓中使用的 Massively Parallel Processing 模型,是将任务并行的分散到多个服务器和节点上,在每个节点上计算完成后,将各自部分的结果汇总在一起得到最终的结果;与 Hadoop 类似,不过前者使用的是 SQL 后者时 MapReduce 程序。
+
+接下来看看各个方法是如何实现的,例如,有两张表 R 和 S,R 共占有 M 页,S 共占有 N 页。r 和 s 分别代表元组,而 i 和 j 分别代表第 i 或者第 j 个字段,也就是后文提到的 JOIN 字段。
+
+
+
+
+## 原理详解
+
+表连接的方式包括了 join, semi-join, outer-join, anti-join/anti-semi-join;实现方式包括了 nested loop, merge, hash,下面使用简单的 nested loop 介绍。
+
+其他的连接方式还有 cross join,用于获取记录的完整笛卡尔积,不能使用 ON 谓词,通常使用不多。
+
+
+inner-join
+自然连接。查看申请表中相关学校的信息。
+
+select * from College c inner join Apply a on c.name = a.name;
+
+for x in ( select * from tablea )
+ for y in ( select * from tableb )
+ if ( x.id == y.id )
+ OutPut_Record(x.*, y.*)
+ end if
+ end loop
+end loop
+
+select * from tablea a left join tableb b on a.id = b.id;
+
+for x in ( select * from tablea )
+ find_flag=false;
+ for y in ( select * from tableb)
+ if ( x.id == y.id )
+ OutPut_Record(x.*, y.*)
+ find_flag=true
+ end if
+ end loop
+ if ( find_flag == false )
+ OutPut_Record(x.*, null)
+ end if
+end loop
+
+select * from Student where exists (select null from Apply where Apply.id = Student.id);
+select * from Student where exists (select * from Apply where Apply.id = Student.id);
+
+for x in ( select * from tablea )
+ for y in ( select * from tableb)
+ if ( x.id == y.id )
+ OutPut_Record(x)
+ Break;
+ end if
+ end loop
+end loop
+
+
+
+anti-join
+现在要求我们找出还没有申请学校的学生信息
+
+select * from Student where not exists (select * from Apply where Apply.id = Student.id);
+
+for x in ( select * from Student )
+ for y in ( select * from Apply )
+ if ( x.deptno != y.deptno )
+ OutPut_Record(x.dname,y.deptno)
+ End if
+ end loop
+end loop
+
+
+
+
+
+
+
+### 其它
+
+尽量避免子查询,而用 JOIN。
+
+STRAIGHT JOIN 完全等同于 INNER JOIN,不过可以实现强制多表的载入顺序,从左到右。默认,JOIN 语法是根据 "哪个表的结果集小,就以哪个表为驱动表" 来决定谁先载入的,而 STRAIGHT JOIN 会强制选择其左边的表先载入。
+
+使用 NATURAL JOIN 时,MySQL 将表中具有相同名称的字段自动进行记录匹配,而这些同名字段类型可以不同。因此,NATURAL JOIN 不用指定匹配条件。
+
+
+
+
+
+
+
+
+### Nested-Loop Join, NLJ
+
+从第一个表每次读一行数据,传递到一个嵌套循环(处理join中的下一个表)。
+这个处理重复次数跟join中涉及的表相同?
+例子:
+Table Join Type
+t1 range
+t2 ref
+t3 ALL
+逻辑处理:
+for each row in t1 matching range {
+ for each row in t2 matching reference key {
+ for each row in t3 {
+ if row satisfies join conditions,
+ send to client
+ }
+ }
+}
+
+### Block Nested-Loop Join, BNL
+
+通过缓存外层循环读的行,来降低内层表的读取次数。比如: 10行数据读入到buffer中,
+然后buffer被传递到内层循环,内层表读出的每一行都要跟这个缓存的10行依次做对比,
+这样就降低了内层表数据的读取次数。
+使用条件:
+1] join_buffer_size决定了每一个join buffer的大小
+2] 只有当join type是 all or index(没有合适的索引,使用全索引或者全表扫描的场景),
+ range的时候才会使用。5.6中,外连接也可以用buffer了。
+3] 每一个需要buffer的join都会申请一个独立的buffer,也就是说一个查询可能使用多个join buffer。
+4] 第一个非常量表是不会使用join buffer的。
+5] join buffer在执行join之前申请,在查询完成后释放。
+6] join buffer只保存跟join有关的列,而不是整行
+
+explain 列显示:Using join buffer (Block Nested Loop)
+
+逻辑处理:
+for each row in t1 matching range {
+ for each row in t2 matching reference key {
+ store used columns from t1, t2 in join buffer
+ if buffer is full {
+ for each row in t3 {
+ for each t1, t2 combination in join buffer {
+ if row satisfies join conditions,
+ send to client
+ }
+ }
+ empty buffer
+ }
+ }
+}
+
+if buffer is not empty {
+ for each row in t3 {
+ for each t1, t2 combination in join buffer {
+ if row satisfies join conditions,
+ send to client
+ }
+ }
+}
+
+### Multi-Range Read, MRR
+
+当一个表很大,不能存储到存储引擎的缓存的时候,使用二级索引做范围扫描会引起大量磁盘随机读。
+MRR的存在就是为了优化这些随机读。mysql开始只扫描跟行相关的索引和收集key,然后把这些key排序,
+最后根据排好序的primary key来从基础表获取数据。 MRR的目的是,降低随机的磁盘IO,替换成相对更
+有顺序的IO。
+MRR的好处:
+1、随机IO转换成顺序IO。
+2、批量处理请求
+
+优化场景:
+A: MRR可以用来做innodb,myiasm的索引范围扫描和等值join操作。
+1、索引元组累积到一个buffer
+2、buffer中的元组根据rowid排序
+3、根据排序好的索引元组顺序去获取数据行
+4、当不需要回表访问的时候,MRR就失去意义了(比如覆盖索引)
+
+当使用MRR的时候 explain出现:Using MRR标志
+
+存储引擎使用read_rnd_buffer_size 的值来确定MRR时的buffer大小。
+
+### Batched Key Access, BKA
+
+当使用索引访问第二个join对象的时候,跟BNL类似,BKA使用一个join buffer
+
+来收集第一个操作对象生成的相关列值。BKA构建好key后,批量传给引擎层做索引
+查找。key是通过MRR接口提交给引擎的,这样,MRR使得查询更有效率。
+BKA使用join buffer size来确定buffer的大小,buffer越大,访问右侧表就越
+顺序。
+使用BAK的条件:
+SET optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';
+
+
+
+
+
+
+
+
+
+
+
+
+
+
嵌套循环连接
+嵌套循环连接(Nested Loop Join),嵌套循环连接由两个 FOR 循环构成,其思路相当的简单和直接,对于关系 R 的每个元组 r 将其与关系 S 的每个元组 s 在 JOIN 条件的字段上直接比较并筛选出符合条件的元组。
+
+Nest Loop Join的操作过程很简单,很想我们最简单的排序检索算法,两层循环结构。进行连接的两个数据集合(数据表)分别称为外侧表(驱动表)和内测表(被驱动表)。首先处理外侧表中每一行符合条件的数据,之后每一行数据和内测表进行连接匹配操作。最后可以获取到结果集合。
+
+http://database.51cto.com/art/200904/117947.htm
+
+foreach tuple r in R do
+ foreach tuple s in S do
+ if ri == sj then add r,s to result
+
+For each block A in R do
+ For each block B in S do
+ For each tuple X in A do
+ For each tuple Y in B do
+ If P(X, Y)
+ Then Result=Result+(X, Y)
+ End
+ End
+ End
+End
+
+
+假设有关系 R 和 S,则在进行连接之前先让 R 和 S 是有序的,然后分别对两个表进行扫描一遍即可完成。
+
+R、S 中元组按连接属性从小到大排序,i=j=0
+while i<len(R) AND j<len(S) do
+ while S[j] != R[i] do
+ while S[j]<R[i] do
+ j=j+1
+ end
+ while S[j]>R[i] do
+ i=i+1
+ end
+ end
+ m=i, n=j
+ while S[m]=S[i] do
+ M=m+1
+ end
+ while R[n]=R[j] do
+ N=n+1
+ end
+ for each tuple X from R[i] to R[m - 1] do
+ for each tuple Y from S[j] to S[n-1] do
+ Result=Result+(X, Y)
+ end
+ end
+end
+
+归并连接执行起来非常高效,其时间复杂度是线性的O(n),其中 n 为 R 和 S 中元组数最多的那个关系的元组个数。但是它只能进行等值连接和自然连接,对于其它谓词的连接显得力不从心,并且还要求连接之前对元组进行全排序。
+
+
+
+
+SELECT * FROM t1 LEFT JOIN t2 ON t2.a=t1.a WHERE t2.b < 5 转化为如下
+SELECT * FROM t1 INNER JOIN t2 ON t2.a=t1.a WHERE t2.b < 5 进一步转化为
+SELECT * FROM t1, t2 ON t2.a=t1.a WHERE t2.b < 5 AND t2.a=t1.a
+