初识 MySQL 5.6 新特性、功能

背景:

      之前介绍过 MySQL 5.5 新功能、参数,现在要用MySQL5.6,所以就学习和了解下MySQL5.6新的特性和功能,尽量避免踩坑。在后续的学习过程中文章也会不定时更新。

一:参数默认值的改变

Table Changes to Server Defaults in MySQL 5.6

ParameterOld DefaultNew DefaultVersion
back_log 50 Autosized usingmax_connections 5.6.6
binlog_checksum NONE CRC32 5.6.6
--binlog-row-event-max-size 1024 8192 5.6.6
flush_time 1800 (on Windows) 0 5.6.6
host_cache_size 128 Autosized usingmax_connections 5.6.8
innodb_autoextend_increment 8 64 5.6.6
innodb_buffer_pool_instances 1 8 (platform dependent) 5.6.6
innodb_concurrency_tickets 500 5000 5.6.6
innodb_data_file_path ibdata1:10M:autoextend ibdata1:12M:autoextend 5.6.7
innodb_file_per_table 0 1 5.6.6
innodb_log_file_size 5MB 48MB 5.6.8
innodb_old_blocks_time 0 1000 5.6.6
innodb_open_files 300 Autosized usinginnodb_file_per_table,table_open_cache 5.6.6
innodb_stats_on_metadata ON OFF 5.6.6
join_buffer_size 128KB 256KB 5.6.6
max_allowed_packet 1MB 4MB 5.6.6
max_connect_errors 10 100 5.6.6
open_files_limit 0 Autosized usingmax_connections 5.6.8
performance_schema OFF ON 5.6.6
performance_schema_events_waits_history_long_size 10000 Autosized 5.6.6
performance_schema_events_waits_history_size 10 Autosized 5.6.6
performance_schema_max_cond_instances 1000 Autosized 5.6.6
performance_schema_max_file_instances 10000 Autosized 5.6.6
performance_schema_max_mutex_instances 1000000 Autosized 5.6.6
performance_schema_max_rwlock_instances 1000000 Autosized 5.6.6
performance_schema_max_table_handles 100000 Autosized 5.6.6
performance_schema_max_table_instances 50000 Autosized 5.6.6
performance_schema_max_thread_instances 1000 Autosized 5.6.6
query_cache_size 0 1M 5.6.8
query_cache_type ON OFF 5.6.8
secure_auth OFF ON 5.6.7
sql_mode '' (empty string) NO_ENGINE_SUBSTITUTION 5.6.6
sync_master_info 0 10000 5.6.6
sync_relay_log 0 10000 5.6.6
sync_relay_log_info 0 10000 5.6.6
table_definition_cache 400 Autosized usingtable_open_cache 5.6.8
table_open_cache 400 2000 5.6.8
thread_cache_size 0 Autosized usingmax_connections 5.6.8

其中比较重要的参数:

表空间增长大小:innodb_autoextend_increment
是否独享表空间:innodb_file_per_table
查询information_schema元数据库里的表时,Innodb还会随机提取其他数据库每个表索引页的部分数据:innodb_stats_on_metadata
主从复制时候的刷写:sync_master_info、sync_relay_log、sync_relay_log_info

二 功能提升:

1)字段类型Timestampdatetime的改变:时间类型字段,4个字节。更多的信息见官方文档可以为表设置多个Timestamp的特性,且datetime类型支持DEFAULT CURRENT_TIMESTAMP和ON UPDATE CURRENT_TIMESTAMP。 参考:MySQL5.6中TIMESTAMP的变化

时间戳。范围是'1970-01-01 00:00:00'到2037年。
TIMESTAMP列用于INSERT或UPDATE操作时记录日期和时间。如果你不分配一个值,表中的第一个TIMESTAMP列自动设置为最近操作的日期和时间。也可以通过分配一个NULL值,将TIMESTAMP列设置为当前的日期和时间。
TIMESTAMP值返回后显示为'YYYY-MM-DD HH:MM:SS'格式的字符串,显示宽度固定为19个字符。如果想要获得数字值,应在TIMESTAMP 列添加+0

 在MySQL 5.6.6之前,TIMESTAMP的默认行为:

  • TIMESTAMP列如果没有明确声明NULL属性,默认为NOT NULL。(而其他数据类型,如果没有显示声明为NOT NULL,则允许NULL值。)设置TIMESTAMP的列值为NULL,会自动存储为当前timestamp。
  • 表中的第一个TIMESTAMP列,如果没有声明NULL属性、DEFAULT或者 ON UPDATE,会自动分配 DEFAULT CURRENT_TIMESTAMP和ON UPDATE CURRENT_TIMESTAMP 属性。
  • 表中第二个TIMESTAMP列,如果没有声明为NULL或者DEFAULT子句,默认自动分配’0000-00-00 00:00:00′。插入行时没有指明改列的值,该列默认分配’0000-00-00 00:00:00′,且没有警告。

Timestamp在5.5和5.6中的表现:5.6取消了一张表只有一个timestamp特性的限制。

MySQL 5.5:
zjy@192.168.210.245 : test 05:58:13>create table tmp_a(id int,a timestamp,b timestamp,c timestamp)engine = innodb default charset utf8;
Query OK, 0 rows affected (0.33 sec)

zjy@192.168.210.245 : test 05:58:50>show create table tmp_a\G;
*************************** 1. row ***************************
       Table: tmp_a
Create Table: CREATE TABLE `tmp_a` (
  `id` int(11) DEFAULT NULL,
  `a` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `b` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `c` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.01 sec)

#已经有了一个timestamp熟悉,再创建一个。 
zjy@192.168.210.245 : test 05:58:57>alter table tmp_a modify b timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;                                          
ERROR 1293 (HY000): Incorrect table definition; there can be only one TIMESTAMP column with CURRENT_TIMESTAMP in DEFAULT or ON UPDATE clause   #失败

MySQL 5.6:
mysql> create table tmp_a(id int,a timestamp,b timestamp,c timestamp)engine = innodb default charset utf8;
Query OK, 0 rows affected (0.02 sec)

mysql> show create table tmp_a\G;
*************************** 1. row ***************************
       Table: tmp_a
Create Table: CREATE TABLE `tmp_a` (
  `id` int(11) DEFAULT NULL,
  `a` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `b` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `c` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00'
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

#已经有了一个timestamp熟悉,再创建一个。  
mysql> alter table tmp_a modify b timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP; 
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> alter table tmp_a modify c timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP; 
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0
#成功
View Code

datetime 类型改进:支持DEFAULT CURRENT_TIMESTAMP和ON UPDATE CURRENT_TIMESTAM。

mysql> show create table tmp_time\G;
*************************** 1. row ***************************
       Table: tmp_time
Create Table: CREATE TABLE `tmp_time` (
  `id` int(11) DEFAULT NULL,
  `day` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `cc` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=latin1


mysql> select * from tmp_time;
+------+---------------------+---------------------+
| id   | day                 | cc                  |
+------+---------------------+---------------------+
|    1 | 2015-08-21 15:50:12 | 2015-08-21 15:50:12 |
|    2 | 2015-08-21 15:50:12 | 2015-08-21 15:50:12 |
+------+---------------------+---------------------+
View Code

特别注意:在MySQL5.6里,特别为timestamp新增了一个参数:explicit_defaults_for_timestamp该参数默认是关闭的,所以在启动的时候,错误日志里面会出现:

[Warning] TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_timestamp server option (see documentation for more details).

上面5.6的测试都是在默认情况下进行的,下面开启这个参数试试,在my.cnf里的mysqld选项组里添加:

explicit_defaults_for_timestamp  = 1

测试参数:

mysql> create table tmp_a(id int,a timestamp,b timestamp,c timestamp not null,d timestamp)engine = innodb default charset utf8;
Query OK, 0 rows affected (0.02 sec)

mysql> show create table tmp_a\G;
*************************** 1. row ***************************
       Table: tmp_a
Create Table: CREATE TABLE `tmp_a` (
  `id` int(11) DEFAULT NULL,
  `a` timestamp NULL DEFAULT NULL,    #开启参数之后,timestamp可以为NULL了。
  `b` timestamp NULL DEFAULT NULL,
  `c` timestamp NOT NULL,
  `d` timestamp NULL DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

#开启参数也一样可以支持多个timestamp特性
mysql> alter table tmp_a modify a timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;  
Query OK, 0 rows affected (0.02 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> alter table tmp_a modify b timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;  
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> insert into tmp_a(id) values(1),(2);
Query OK, 2 rows affected, 1 warning (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 1

mysql> show warnings;
+---------+------+----------------------------------------+
| Level   | Code | Message                                |
+---------+------+----------------------------------------+
| Warning | 1364 | Field 'c' doesn't have a default value |
+---------+------+----------------------------------------+
1 row in set (0.00 sec)

mysql> show create table tmp_a\G;
*************************** 1. row ***************************
       Table: tmp_a
Create Table: CREATE TABLE `tmp_a` (
  `id` int(11) DEFAULT NULL,
  `a` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `b` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `c` timestamp NOT NULL,     #不为null的值且没有默认值,写入会报warning,用“0000-00-00 00:00:00”填充。
  `d` timestamp NULL DEFAULT NULL 
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

mysql> select * from tmp_a;
+------+---------------------+---------------------+---------------------+------+
| id   | a                   | b                   | c                   | d    |
+------+---------------------+---------------------+---------------------+------+
|    1 | 2015-08-20 06:21:53 | 2015-08-20 06:21:53 | 0000-00-00 00:00:00 | NULL |
|    2 | 2015-08-20 06:21:53 | 2015-08-20 06:21:53 | 0000-00-00 00:00:00 | NULL |
+------+---------------------+---------------------+---------------------+------+
2 rows in set (0.01 sec)
View Code

所以MySQL5.6开启该参数之后,timestamp字段类型的行为是:

  • TIMESTAMP如果没有显示声明NOT NULL,是允许NULL值的,可以直接设置改列为NULL,而没有默认填充行为。
  • TIMESTAMP不会默认分配DEFAULT CURRENT_TIMESTAMP 和 ON UPDATE CURRENT_TIMESTAMP属性。
  • 声明为NOT NULL且没有默认子句的TIMESTAMP列是没有默认值的。往数据表中插入列,又没有给TIMESTAMP列赋值时,如果是严格SQL模式,会抛出一个错误,如果严格SQL模式没有启用,该列会赋值为’0000-00-00 00:00:00′,同时出现一个警告。(这和MySQL处理其他时间类型数据一样,如DATETIME)

2)InnoDB的增强

全文索引的支持(Fulltext index):MySQL5.6中内置了对Innodb的全文索引支持。5.6.4之前版本的MySQL只有MyISAM支持全文搜索。对于全文索引的概念和使用方法可以自行在网上搜索,比如MySQL必知必会第18章

全文搜索的基础是FULLTEXT索引,在MySQL5.6.4以后的版本中可以在Innodb,MyISAM数据表里的CHAR,VARCHAR和TEXT字段创建。
全文搜索在MyISAM表中,将忽略至少在一半数据行中出现的单词(Innodb没有此限制)。
全文搜索会忽略一些常见单词,如“a”,“the”等,这些单词被成为“停止词”。(可以修改停止词)
FULLTEXT索引可以为一个或多个数据列创建。如果搜索涉及多个数据列,必须为多个数据索引。FULLTEXT(title,address)和FULLTEXT(address,title)效果是一样的,搜索时使用顺序也是不重要的。
InnoDB Fulltext的新增的参数:(加粗部分是InnoDB的参数)
mysql> show variables like '%ft%';
+---------------------------------+----------------+
| Variable_name                   | Value          |
+---------------------------------+----------------+
| ft_boolean_syntax               | + -><()~*:""&| |
| ft_max_word_len                 | 84             |
| ft_min_word_len                 | 4              |
| ft_query_expansion_limit        | 20             |
| ft_stopword_file                | (built-in)     |
| innodb_ft_aux_table             |                |
| innodb_ft_cache_size            | 8000000        |
| innodb_ft_enable_diag_print     | OFF            |
| innodb_ft_enable_stopword       | ON             |
| innodb_ft_max_token_size        | 84             |
| innodb_ft_min_token_size        | 3              |
| innodb_ft_num_word_optimize     | 2000           |
| innodb_ft_result_cache_limit    | 2000000000     |
| innodb_ft_server_stopword_table |                |
| innodb_ft_sort_pll_degree       | 2              |
| innodb_ft_total_cache_size      | 640000000      |
| innodb_ft_user_stopword_table   |                |
+---------------------------------+----------------+

现在来说明下MyISAM和InnoDB对全文索引的使用。

1、索引长度的设置:

#MyISAM
ft_min_word_len         = 1
ft_max_word_len84
#InnoDB
innodb_ft_min_token_size= 1
innodb_ft_max_token_size=84

2、停止词的设置(stopword):

#MyISAM  停止词存放在文件
ft_stopword_file        = /var/lib/mysql/stopword.txt

#InnoDB,停止词存放在表
innodb_ft_server_stopword_table = dbname/ft_stopwords
innodb_ft_user_stopword_table = dbname/ft_stopwords

3、测试

mysql> show create table tmp_mfulltext\G;
*************************** 1. row ***************************
       Table: tmp_mfulltext
Create Table: CREATE TABLE `tmp_mfulltext` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(30) DEFAULT NULL,
  `content` text,
  FULLTEXT KEY `idx_content` (`content`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8

mysql> show create table tmp_ifulltext\G;
*************************** 1. row ***************************
       Table: tmp_ifulltext
Create Table: CREATE TABLE `tmp_ifulltext` (
  `id` int(11) DEFAULT NULL,
  `name` varchar(30) DEFAULT NULL,
  `content` text,
  FULLTEXT KEY `idx_content` (`content`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

mysql> select * from tmp_mfulltext where MATCH(content) AGAINST('Auxiliary');  #正常
+------+------+---------------------------------------------------------------------------------+
| id   | name | content                                                                         |
+------+------+---------------------------------------------------------------------------------+
|    2 | jin  | Auxiliary index table names are prefixed with FTS_ and postfixed with INDEX_*.  |
+------+------+---------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> select * from tmp_ifulltext where MATCH(content) AGAINST('Auxiliary');  #正常
+------+------+---------------------------------------------------------------------------------+
| id   | name | content                                                                         |
+------+------+---------------------------------------------------------------------------------+
|    2 | jin  | Auxiliary index table names are prefixed with FTS_ and postfixed with INDEX_*.  |
+------+------+---------------------------------------------------------------------------------+
1 row in set (0.01 sec)

mysql> select * from tmp_mfulltext where MATCH(content) AGAINST('when'); #搜索长度大于ft_min_word_len,但是没有找到结果。
Empty set (0.00 sec)

mysql> select * from tmp_ifulltext where MATCH(content) AGAINST('when'); #搜索长度大于innodb_ft_min_token_size,但是没有找到结果。
Empty set (0.00 sec)

原因可能就停止词(stopword)的问题,之前介绍过:MySQL全文检索Stopwords的设置

手动指定停止词后:
mysql> repair table tmp_mfulltext;  #需要修复下才能进行match
+------------------------+--------+----------+----------+
| Table                  | Op     | Msg_type | Msg_text |
+------------------------+--------+----------+----------+
| dba_test.tmp_mfulltext | repair | status   | OK       |
+------------------------+--------+----------+----------+
1 row in set (0.00 sec)

mysql> select count(*) from tmp_mfulltext where MATCH(content) AGAINST('when');#停止词的问题,指定好之后有结果。
+----------+
| count(*) |
+----------+
|        1 |
+----------+
1 row in set (0.00 sec)

mysql> select * from tmp_ifulltext where MATCH(content) AGAINST('when');  #没有结果,这里说明Innodb的全部文索停止词和MyISAM的不一样。
Empty set (0.01 sec)

关于InnoDB fulltext的停止词相关的设置,默认的停止词表是:

mysql> select * from INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD;
36 rows in set (0.00 sec)

手动设置停止表:

#因为InnoDB的停止词是以表形式存的,先建立停止词表:停止词表必须是InnoDB表,只包含一个VARCHAR类型名为VALUE的列
mysql> create table innodb_ft_stopwords(value varchar(30))ENGINE=INNODB;  #结构和INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD一样
Query OK, 0 rows affected (0.01 sec)

#插入停止词:
mysql> INSERT INTO innodb_ft_stopwords(value) VALUES('a');
Query OK, 1 row affected (0.00 sec)

#加载停止词表,注意格式
mysql> SET GLOBAL innodb_ft_server_stopword_table = 'dba_test/innodb_ft_stopwords';
Query OK, 0 rows affected (0.00 sec)

mysql> SET GLOBAL innodb_ft_user_stopword_table = 'dba_test/innodb_ft_stopwords';
Query OK, 0 rows affected (0.00 sec)

#因为fulltext的创建是在加载停止词之后的,之前建立的fulltext找不到结果。
mysql> select * from tmp_ifulltext where MATCH(content) AGAINST('when');
Empty set (0.00 sec)

#optimize table 之后可以
mysql> optimize table tmp_ifulltext; 
+------------------------+----------+----------+-------------------------------------------------------------------+
| Table                  | Op       | Msg_type | Msg_text                                                          |
+------------------------+----------+----------+-------------------------------------------------------------------+
| dba_test.tmp_ifulltext | optimize | note     | Table does not support optimize, doing recreate + analyze instead |
| dba_test.tmp_ifulltext | optimize | status   | OK                                                                |
+------------------------+----------+----------+-------------------------------------------------------------------+
2 rows in set (0.07 sec)

#搜索出结果
mysql> select count(*) from tmp_ifulltext where MATCH(content) AGAINST('when');
+----------+
| count(*) |
+----------+
|        1 |
+----------+
1 row in set (0.00 sec)

这里需要注意的是在创建全文索引之前就应先设置好停止词表,不然就得执行optimize。停止词表确定之后,请加入到配置文件中:

innodb_ft_server_stopword_table = dba_test/innodb_ft_stopwords
innodb_ft_user_stopword_table = dba_test/innodb_ft_stopwords 

Tips:
· 全文搜索在MyISAM表中,将忽略至少在一半数据行中出现的单词,而Innodb没有此限制:

mysql> select count(*) from tmp_ifulltext where MATCH(content) AGAINST('the');
+----------+
| count(*) |
+----------+
|        6 |
+----------+
1 row in set (0.00 sec)

mysql> select count(*) from tmp_mfulltext where MATCH(content) AGAINST('the');
+----------+
| count(*) |
+----------+
|        0 |
+----------+
1 row in set (0.00 sec)

关于更多的信息请查看官方文档MySQL5.6 InnoDB FULLTEXTIndexes研究测试

② 独立表空间的.ibd文件可以在建表时指定目录:

#生成测试数据
mysql>  SHOW VARIABLES LIKE 'innodb_file_per_table';
+-----------------------+-------+
| Variable_name         | Value |
+-----------------------+-------+
| innodb_file_per_table | ON    |
+-----------------------+-------+
1 row in set (0.00 sec)

mysql> CREATE TABLE t1 (c1 INT PRIMARY KEY) DATA DIRECTORY = '/tmp/mysqldata/'; #会在该目录下再生成一个以数据库命名的目录
Query OK, 0 rows affected (0.02 sec)

mysql> insert into t1 values(1),(2),(3);
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

#查看物理文件
root@zjy:~# ls -lh /var/lib/mysql3/dba_test/
-rw-rw---- 1 mysql mysql 8.4K  8月 21 00:44 t1.frm
-rw-rw---- 1 mysql mysql   30  8月 21 00:44 t1.isl

root@zjy:~# ls -lh /tmp/mysqldata/dba_test/
-rw-rw---- 1 mysql mysql 96K  8月 21 00:45 t1.ibd

上面被看到数据文件.ibd和表结构定义文件.frm已分离。注意:表空间分离表会产生一个table_name.isl的连接。

③ 导入和导出表空间:复制文件比mysqldump&restore快

#生成测试数据
server1:
mysql> CREATE TABLE test (c1 INT PRIMARY KEY)engine = innodb;
Query OK, 0 rows affected (0.02 sec)

mysql> insert into test values(1),(2),(3);
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

server2:
mysql> CREATE TABLE test (c1 INT PRIMARY KEY)engine = innodb;
Query OK, 0 rows affected (0.02 sec)

mysql> insert into test values(1),(3);
Query OK, 3 rows affected (0.00 sec)
Records: 3  Duplicates: 0  Warnings: 0

#接着把server2的表空间给discard掉:
root@zjy:/var/lib/mysql/dba_test# ls -lh test.*
-rw-rw---- 1 mysql mysql 8.4K  8月 21 01:37 test.frm
-rw-rw---- 1 mysql mysql  96K  8月 21 01:37 test.ibd
#discard 表空间
mysql> ALTER TABLE test DISCARD TABLESPACE;
Query OK, 0 rows affected (0.00 sec)
#表空间没了
root@zjy:/var/lib/mysql/dba_test# ls -lh test.*
-rw-rw---- 1 mysql mysql 8.4K  8月 21 01:37 test.frm

#然后把server1的表空间复制给server2:
mysql> FLUSH TABLES test FOR EXPORT;  #必须要生成cfg metadata文件
Query OK, 0 rows affected (0.00 sec)

root@zjy:/var/lib/mysql3/dba_test# ls -lh test.*
-rw-rw---- 1 mysql mysql  330  8月 21 01:40 test.cfg
-rw-rw---- 1 mysql mysql 8.4K  8月 21 01:36 test.frm
-rw-rw---- 1 mysql mysql  96K  8月 21 01:36 test.ibd

#需要把ibd和cfg文件复制到server2上
root@zjy:/var/lib/mysql3/dba_test# cp test.cfg /var/lib/mysql/dba_test/
root@zjy:/var/lib/mysql3/dba_test# cp test.ibd /var/lib/mysql/dba_test/
#最后修改server2文件权限和import表空间:
root@zjy:/var/lib/mysql/dba_test# chown -R mysql.mysql *
mysql> ALTER TABLE test IMPORT TABLESPACE;
Query OK, 0 rows affected (0.02 sec)
#值从1,3变成了1,23。数据已成功迁移过来
mysql> select * from test;
+----+
| c1 |
+----+
|  1 |
|  2 |
|  3 |
+----+
#最后解锁server1上的锁:
mysql> unlock tables;

3)innodb内部性能增强:

将flushing操作独立出主线程,减少核心互斥锁。可设置多个清除线程,减少内存资源争夺。回收UNDO,通过innodb_purge_threads 来开启,在5.5中只能也只有1可以设置。5.6可以设置大于1。刷写脏页,5.6之后支持,Page cleaner线程,从Master Thread独立出来,减轻了Master Thread 的工作和对用户查询的阻塞。进一步提高Innodb 存储引擎的性能和并发。

② 检测死锁算法增强。在非递归情况下死锁检测:死锁信息不止可以通过show engine innodb status一种方法查看,还可以记录到 error 日志,并且可以查看历史的死锁,方便分析。开启参数:innodb_print_all_deadlocks,默认关闭。

 buffer pool的预热:

如果一台高负荷的机器重启后,buffer pool中的热数据被丢失,此时就会重新从磁盘加载到Buffer_Pool缓冲池里,这样当高峰期间,性能就会变得很差,连接数就会很高,应用的性能也受到影响。MySQL5.6里,一个新特性避免的这种问题的出现,在配置文件里添加:

#在关闭时把热数据dump到本地磁盘 #5.7.7默认开启
innodb_buffer_pool_dump_at_shutdown = 1
#在启动时把热数据加载到内存 #5.7.7默认开启
innodb_buffer_pool_load_at_startup = 1
#采用手工方式把热数据dump到本地磁盘
innodb_buffer_pool_dump_now = 1
#采用手工方式把热数据加载到内存
innodb_buffer_pool_load_now = 1

参数innodb_buffer_pool_dump_at_shutdowninnodb_buffer_pool_load_at_startup 是数据库关闭开启时候起作用的:

当数据库正常关闭/重启之后,错误日志里面会记录:会生一个ib_buffer_pool的文件,这个文件名字有参数 innodb_buffer_pool_filename 控制。

2015-08-21 19:55:07 7fe34dff8700 InnoDB: Dumping buffer pool(s) to .//ib_buffer_pool
2015-08-21 19:55:07 7fe34dff8700 InnoDB: Buffer pool(s) dump completed at 150821 19:55:07
2015-08-21 19:55:08 31943 [Note] InnoDB: Shutdown completed; log sequence number 3052155687

当数据库重新起来之后,错误日志里会记录:

2015-08-21 19:55:59 7f5eaf7f8700 InnoDB: Buffer pool(s) load completed at 150821 19:55:59

参数innodb_buffer_pool_dump_nowinnodb_buffer_pool_load_now 是需要手动执行的:

当数据库正在运行时执行:

查看BP里dump文件的时间:
root@zjy:/var/lib/mysql# ls -lh ib_buffer_pool
-rw-rw---- 1 mysql mysql 52K  8月 25 18:30 ib_buffer_pool

执行:
mysql> SET GLOBAL innodb_buffer_pool_dump_now=ON;
Query OK, 0 rows affected (0.01 sec)

查看BP里dump文件的时间:时间变成当前时间,说明dump成功。
root@zjy:/var/lib/mysql# ls -lh ib_buffer_pool
-rw-rw---- 1 mysql mysql 52K  8月 25 19:02 ib_buffer_pool

load dump文件到BP:
mysql> SET GLOBAL innodb_buffer_pool_load_now=ON;
Query OK, 0 rows affected (0.01 sec)

显示BP导入、导出的状态:重启数据库虽然在日志文件里面可以查看到dump和load的一些信息,但在运行时,也可以监控是否成功:

#监控查看BP dump的状态:Innodb_buffer_pool_dump_status,手动执行BP的dump动作
mysql> select now();SET GLOBAL innodb_buffer_pool_dump_now=ON;SHOW STATUS LIKE 'Innodb_buffer_pool_dump_status';
+---------------------+
| now()               |
+---------------------+
| 2015-08-25 19:08:49 |
+---------------------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

+--------------------------------+--------------------------------------------------+
| Variable_name                  | Value                                            |
+--------------------------------+--------------------------------------------------+
| Innodb_buffer_pool_dump_status | Buffer pool(s) dump completed at 150825 19:08:49 | #和dump执行的时间一致,说明dump成功,是上一个dump的结果
+--------------------------------+--------------------------------------------------+
1 row in set (0.00 sec)

#监控查看BP load的状态:Innodb_buffer_pool_load_status,手动执行load dump文件动作
mysql> select now();SET GLOBAL innodb_buffer_pool_load_now=ON;SHOW STATUS LIKE 'Innodb_buffer_pool_load_status';
+---------------------+
| now()               |
+---------------------+
| 2015-08-25 19:10:21 |
+---------------------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

+--------------------------------+--------------------------------------------------+
| Variable_name                  | Value                                            |
+--------------------------------+--------------------------------------------------+
| Innodb_buffer_pool_load_status | Buffer pool(s) load completed at 150825 19:10:21 | #和load执行的时间一致,说明load成功,是还原上一个dump文件
+--------------------------------+--------------------------------------------------+
1 row in set (0.00 sec)

通过show status 查看之外,还可以通过在information_schema里查看:

dump:
SELECT variable_value FROM information_schema.global_status WHERE variable_name = 'INNODB_BUFFER_POOL_DUMP_STATUS';

load:
SELECT variable_value FROM information_schema.global_status WHERE
variable_name = 'INNODB_BUFFER_POOL_LOAD_STATUS';

此外,也可以中止BP的load,参数:innodb_buffer_pool_load_abort,中止缓冲池加载操作。

#在MySQL里运行:
mysql> set global innodb_buffer_pool_load_abort = on;
Query OK, 0 rows affected (0.00 sec)

#也可以在配置文件里添加:
innodb_buffer_pool_load_abort    = 1

另外,innodb_buffer_pool_dump_pct控制备份buffer pool的百分比,5.7中支持。5.6默认dump所有的Buffer pool。
总之,引入看BP的自动预热解决了数据库维护重启的时候导致性能变差的问题。

注意:只有在正常关闭MySQL服务,或者pkill mysql时,会把热数据dump到内存。机器宕机或者pkill -9 mysql,是不会dump。

④ 新增参数innodb_page_size设置页大小。指定在一个MySQL实例的所有InnoDB表空间的页大小。该值是在创建实例时设置的,并且在随后的情况中保持不变。你可以使用16K指定页面大小(默认),8K,或4K。或者,您可以指定字节的页大小(4096,8192,16384)。16K页面适合大部分场景,特别是表扫描、DML操作和批量更新;较小的页面可能更是适合OLTP和SSD存储设备。

 undo log可独立出系统表空间,原来存在ibd系统表空间里。undo log分离到独立的表空间,并放到单独的文件目录下。对于并发写入型负载,我们可以把undo文件部署到单独的高速存储设备上。缺点就是不能进行回收(收缩)空间大小。直到MySQL5.7 ,才支持在线收缩。由参数innodb_undo_tablespacesinnodb_undo_directory控制:需要在数据库实例建立之后就开启,否则会初始化innodb 失败,开启之后不能修改。具体信息可以看这篇文章

在配置文件里设置:
innodb_undo_directory = /var/lib/mysql4/undolog/ #undo log 存放的目录
innodb_undo_tablespaces = 10   #默认表空间的个数,大小默认是10M,以undo001形式存在,值为0则表示使用系统表空间。

另外一个参数是:inodb_undo_logs:代替了先前的参数innodb_rollback_segments,控制着回滚段的数量(注意范围是0-128) 默认不指定的时候是128个回 滚段。(注意要想增加回滚段的时候必须要重启mysql)。

要是开启的时候遇到innodb数据初始化失败:Plugin 'InnoDB' init function returned error。则需要先把数据dump出来,删除掉ibdata、ib_logfile等文件,再开启。

 优化器统计持续化:重启不丢失。缓冲池flush算法增强、支持read-only事务、redo log最大增至512G。

⑦ 支持使用enosql api访问innodb表、redo log最大增至512G、INFORMATION_SCHEMA新增相关字典表若干。 InnoDB更多的改进:http://dev.mysql.com/doc/refman/5.6/en/optimizing-innodb.html

4)复制、日志功能增强

① binlog_row_image 参数控制Row模式下的binlog记录格式。在MySQL5.6只之前,Row模式记录所有列,即使是修改一个列。这样就存在一个问题,如果表中包含很多的大字段,表的单行长度就会非常长,这样每次update就会导致大量的 binlog空间生成,具体的可以看这里。该参数可以控制ROW下的记录格式,可以动态修改,3个值可以设置:

full:默认行为,记录所有的列,和MySQL5.6之前的版本一样。
minimal:记录被修改的列,其他未修改的列不记录。
noblob :记录所有的列,除text、blob列外。

测试:

BINLOG:
binlog_format           = ROW
binlog_row_image        = minimal

SQL:
update doctors set username ='XXXYYYZZZ',cellphone='110110110110',modifyTime=now() where id = 101;

LOG:  #记录了纸杯修改的三个列
### UPDATE `dba_test`.`doctors`
### WHERE
###   @1=101 /* INT meta=0 nullable=0 is_null=0 */
### SET
###   @4='XXXYYYZZZ' /* VARSTRING(192) meta=192 nullable=0 is_null=0 */
###   @13='110110110110' /* VARSTRING(48) meta=48 nullable=1 is_null=0 */
###   @19='2015-08-28 11:20:09' /* DATETIME(0) meta=0 nullable=1 is_null=0 */

其他2个可以自行测试。

所以在5.6中binlog_row_image设置为minimal,这样就可以大大减小了binlog的长度,进而减少了空间的使用。但不能通过脚本实现回滚了。

② log_bin_basenamelog_bin_indexrelay_log_basename,显示binlog和relay log 日志名字,直接用log_bin指定。5.5和5.6的区别如下:

mysql> show global variables like 'log%';
+---------------------------------+-------------------------------+
| Variable_name                   | Value                         |
+---------------------------------+-------------------------------+
| log_bin                         | ON                            |            #看不到,需要到配置文件、目录里找。
+---------------------------------+-------------------------------+


mysql> show global variables like 'log%';
+----------------------------------------+------------------------------------+
| Variable_name                          | Value                              |
+----------------------------------------+------------------------------------+
| log_bin                                | ON                                 |
| log_bin_basename                       | /var/lib/mysql/mysql-bin_06        | #可以看到名称和路径,即log_bin配置。
| log_bin_index                          | /var/lib/mysql/mysqld-bin_06.index | #索引文件
+----------------------------------------+------------------------------------+



zjy@192.168.100.2 : analyze_data 05:12:57>show variables like 'relay%';
+-----------------------+-----------------------+
| Variable_name         | Value                 |
+-----------------------+-----------------------+
| relay_log             | mysqld-relay-bin-3306 |                  #同上
| relay_log_index       |                       |                 
+-----------------------+-----------------------+


mysql> show global variables like 'relay%';
+---------------------------+-------------------------------------------+
| Variable_name             | Value                                     |
+---------------------------+-------------------------------------------+
| relay_log                 | mysqld-relay-bin3306                      |
| relay_log_basename        | /var/lib/mysql/mysqld-relay-bin3306       |  #同上
| relay_log_index           | /var/lib/mysql/mysqld-relay-bin3306.index |
+---------------------------+-------------------------------------------+
View Code

 master.info和relay-log.info支持存储在表中,通过参数:master-info-repositoryrelay-log-info-repository控制,默认是存文件(file):

mysql> show variables like '%info%'; 
+---------------------------+----------------+
| Variable_name             | Value          |
+---------------------------+----------------+
| master_info_repository    | FILE           |
| relay_log_info_repository | FILE           |
+---------------------------+----------------+

在配置文件里添加:
master_info_repository    = table
relay_log_info_repository = table

mysql> show variables like '%info%'; 
+---------------------------+----------------+
| Variable_name             | Value          |
+---------------------------+----------------+
| master_info_repository    | TABLE          |
| relay_log_info_repository | TABLE          |
+---------------------------+----------------+

这里需要知道的是,设置成Table之后,表存储在mysql数据库中。对应的表名是:slave_master_info,slave_relay_log_info。

 mysqlbinlog命令支持binlog备份。在5.6之前mysqlbinlog只是读取binglog日志,并解析出一个文本格式的内容。5.6增加了一个--raw参数,作用是读取binglog日志,并重新生成binlog格式的文件。其原理就是开启一个IO(dump)线程来拉取binlog日志,下面通过使用方法介绍下:

A Server:
mysql> SHOW BINARY LOGS;
+---------------------+-----------+
| Log_name            | File_size |
+---------------------+-----------+
| mysql-bin_06.000001 |       174 |
| mysql-bin_06.000002 |       201 |
| mysql-bin_06.000003 |       201 |
| mysql-bin_06.000004 |      1864 |
| mysql-bin_06.000005 |      1420 |
| mysql-bin_06.000006 |       191 |
+---------------------+-----------+

B Server:
备份1~6的二进制日志:
1:备份001和002的二进制日志,再备份其他的就继续指定binlog name
mysqlbinlog --read-from-remote-server --user=zjy --password=123456 --host=127.0.0.1 --raw mysql-bin_06.000001 mysql-bin_06.000002

2:从000001开始备份所有的二进制日志:
mysqlbinlog --read-from-remote-server --user=zjy --password=123456 --host=127.0.0.1 --raw --to-last-log mysql-bin_06.000001

3:从000001开始备份所有的二进制日志,命令行不退出,一直监控着日志的轮巡,一有新的event就马上备份。
mysqlbinlog --read-from-remote-server --user=zjy --password=123456 --host=127.0.0.1 --raw --stop-never mysql-bin_06.000001
4:--stop-never-slave-server-id=id,指定binlog server的id,开启多个binlog server,可以用不同的server-id,默认是65535
mysqlbinlog --read-from-remote-server --stop-never-slave-server-id=1 --user=zjy --password=123456 --host=127.0.0.1 --raw --stop-never mysql-bin_06.000001
5:--result-file,指定一个文件目录。
mysqlbinlog --read-from-remote-server --user=zjy --password=123456 --host=127.0.0.1 --raw --stop-never mysql-bin_06.000001 --result-file=/home/zhoujy/

在Server B里面看到从Server A上备份过来的binlog日志,具体的使用方法请见官方文档这篇说明

⑤ 支持多线程复制:slave_parallel_workers多线程复制是基于库的,一个线程也只针对一个数据库。注意:当开启多线程复制后,需要关闭slave_transaction_retries,设置其为0,否则会在start/stop slave中遇到:

slave_transaction_retries is not supported in multi-threaded slave mode. In the event of a transient failure, the slave will not retry the transaction and will stop.

5.6以前的从服务器,有一个io线程负责接收binary log,还有一个sql线程负责执行binary log中的sql语句。如果主服务器的数据更新相当频繁,而从服务器由于某些原因跟不上,会导致从服务器落后比较长的时间。5.6之后采用多个sql线程,每个sql线程处理不同的database,提高了并发性能,即使某database的某条语句暂时卡住,也不会影响到后续对其它的database进行操作。

设置slave_parallel_workers=4:

要把多线程改成单线程,即 slave_parallel_workers 从非0改成0,则执行:

zjy@192.168.110.xx : (none) 02:28:49>stop slave;
Query OK, 0 rows affected (0.30 sec)

zjy@192.168.110.xx : (none) 02:28:55>START SLAVE UNTIL SQL_AFTER_MTS_GAPS;
Query OK, 0 rows affected (0.02 sec)

zjy@192.168.110.xx : (none) 02:29:07>SET @@GLOBAL.slave_parallel_workers = 0;
Query OK, 0 rows affected (0.01 sec)

zjy@192.168.110.xx : (none) 02:29:29>START SLAVE SQL_THREAD;
Query OK, 0 rows affected (0.11 sec)

查看,只有一个线程:

更多的信息见官方文档

支持延迟复制,MASTER_DELAY。在change的时候指定,单位是秒。

CHANGE master TO MASTER_DELAY=600;
mysql> show slave status\G;
*************************** 1. row ***************************
               Slave_IO_State: 
                  Master_Host: 127.0.0.1
                  Master_User: rep
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin3306.000006
          Read_Master_Log_Pos: 23940536
...
...
                    SQL_Delay: 0
...

新增GTID复制,详细信息见:MySQL5.6 新特性之GTID

⑧ binlog支持crash-safe:

binlog_checksum=CRC32        :事件写入二进制进行校验,默认CRC32。

master_verify_checksum    =1 :检查二进制日志,bin log。默认禁止。

slave_sql_verify_checksum =1 :检查中继日志,relay log。默认禁止。

thread_pool_size 的引入:在引入线程池之前,MySQL支持的线程处理方式(thread_handling参数控制)有no-threads和one-thread-per-connection两种方式,no-threads方式是指任一时刻最多只有一个连接可以连接到server,一般用于实验性质。 one-thread-per-connection是指针对每个连接创建一个线程来处理这个连接的所有请求,直到连接断开,线程 结束。是thread_handling的默认方式。one-thread-per-connection存在的问题就是需要为每个连接创建一个新的thread,当并发连接数达到一定程度,性能会有明显下降,因为过多的线程会导致频繁的上下文切换,CPU cache命中率降低和锁的竞争 更加激烈。解决one-thread-per-connection的方法就是降低线程数,这样就需要多个连接共用线程,这便引入了线程池的概念。

thread_handling = pool-of-threads

5)优化器增强 :可以看这里

mysql >show variables like 'optimizer_switch'\G;                                                                                                       *************************** 1. row ***************************
Variable_name: optimizer_switch
        Value: index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=on,mrr_cost_based=on,block_nested_loop=on,batched_key_access=off,materialization=on,semijoin=on,loosescan=on,firstmatch=on,subquery_materialization_cost_based=on,use_index_extensions=on

① MRR优化(Multi-Range Read):mrr=on,mrr_cost_based=on MRR的优化用于range, ref, eq_ref, and Batched Key Access访问方法

Multi-Range Read 多范围读(MRR) 它的作用是基于辅助/第二索引的查询,减少随机IO,并且将随机IO转化为顺序IO,提高查询效率。在没有MRR之前(MySQL5.6之前),先根据where条件中的辅助索引获取辅助索引与主键的集合,再通过主键来获取对应的值。辅助索引获取的主键来访问表中的数据会导致随机的IO(辅助索引的存储顺序并非与主键的顺序一致),不同主键不在同一个page里面时必然导致多次IO 和随机读。使用MRR优化(MySQL5.6之后),先根据where条件中的辅助索引获取辅助索引与主键的集合,再将结果集放在buffer里面(read_rnd_buffer_size 大小直到buffer满了),然后对结果集按照pk_column排序,得到有序的结果集rest_sort。最后利用已经排序过的结果集,访问表中的数据,此时是顺序IO。即MySQL 将根据辅助索引获取的结果集根据主键进行排序,将无序化为有序,可以用主键顺序访问基表,将随机读转化为顺序读,多页数据记录可一次性读入或根据此次的主键范围分次读入,以减少IO操作,提高查询效率。

具体参考:http://blog.itpub.net/22664653/viewspace-1673682/

② ICP优化(Index Condition Pushdown):index_condition_pushdown=on。ICP的优化用于range, ref, eq_ref, and ref_or_null访问方法

Index Condition Pushdown (ICP)是MySQL用索引去表里取数据的一种优化。禁用ICP(MySQL5.6之前),引擎层会利用索引在基表中寻找数据行,然后返回给MySQL Server层,再去为这些数据行进行WHERE后的条件的过滤(回表)。启用ICP(MySQL5.6之后),如果部分WHERE条件能使用索引中的字段,MySQL会把这部分下推到引擎层。存储引擎通过使用索引把满足的行从表中读取出。ICP减少了引擎层访问基表的次数和MySQL Server 访问存储引擎的次数。总之是 ICP的优化在引擎层就能够过滤掉大量的数据,减少io次数,提高查询语句性能。

具体参考:http://blog.itpub.net/22664653/viewspace-1678779/http://www.cnblogs.com/zhoujinyi/archive/2013/04/16/3016223.html

 Limit优化 : 

SELECT … FROM single_table … ORDER BY non_index_column [DESC] LIMIT N [OFFSET M];
当有足够的内存(sort_buffer_size)来存储N+M行记录时, 会在内存中创建一个优先队列来存储数据,这意味着一次扫描表就可以获得想要的数据,避免了创建/写入临时表及归并排序操作(之前版本的逻辑)
大概逻辑为:
1.扫描表,将数据有序的插入到优先队列中,如果队列满了,则按顺序移除多余的记录
2.返回队列中的N行记录,如果指定了OFFSET M,则忽略开始的M条记录,然后返回剩下的N条

④ Index Extension优化 :use_index_extensions=on。

InnoDB的二级索引都包含主键信息,隐性的包含了主键列,可以通过在二级索引上添加主键列来查看(显性的添加主键列到二级索引,索引大小不变)。在MySQL5.6之前,MySQL优化器选择索引时不会考虑到这些隐性的主键列,一般都需要手动添加。而在MySQL5.6之后,优化器选择时,会考虑到二级索引上的primary key。

BKA优化(Batched Key Access):mrr=on,mrr_cost_based=off,batched_key_access=on.

提高表join性能的算法,在介绍BKA之前,先了解下Join表的优化历史:

Nested Loop Join算法:将驱动表/外部表的结果集作为循环基础数据,然后循环该结果集,每次获取一条数据作为下一个表的过滤条件查询数据,然后合并结果,获取结果集返回给客户端。Nested-Loop一次只将一行传入内层循环, 所以外层循环(的结果集)有多少行, 内存循环便要执行多少次,效率非常差。

Block Nested-Loop Join算法:将外层循环的行/结果集存入join buffer, 内层循环的每一行与整个buffer中的记录做比较,从而减少内层循环的次数。主要用于当被join的表上无索引。

Batched Key Access算法:当被join的表能够使用索引时,就先排好顺序,然后再去检索被join的表。对这些行按照索引字段进行排序,因此减少了随机IO。如果被Join的表上没有索引,则使用老版本的BNL策略(BLOCK Nested-loop)。BKA默认是关闭的,要使用BKA,必须调整系统参数optimizer_switch的值,batched_key_access设置为on,因为BKA使用了MRR,因此也要打开MRR,但是基于成本优化MRR算法不是特别准确官方文档推荐关闭mrr_cost_based,将其设置为off。

set optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on'

具体参考:http://blog.itpub.net/22664653/viewspace-1715511/

explain支持对insert、delete、update、replace语句,并且支持json格式的 optimizer_trace

mysql>set optimizer_trace='enabled=on';

mysql>select * from information_schema.optimizer_trace\G;

具体参考:http://hidba.ga/2014/09/24/in-and-range/

子查询优化 :subquery_materialization_cost_based

更多的信息见:http://dev.mysql.com/doc/refman/5.6/en/index.html

in、range优化: eq_range_index_dive_limit

在较多等值查询(例如多值的IN查询)情景中,预估可能会扫描的记录数,从而选择相对更合适的索引。用于优化in(),以确认是否直接使用索引统计,在where条件中列的等值条件个数小于这个值时,使用index dive来估算行数,否则使用index statistics来估算;设置为0则禁用index statistics, index dive更准确但效率低

更多的信息见:http://myrock.github.io/2014/09/24/in-and-range/

...

6)参数说明 : http://dev.mysql.com/doc/refman/5.6/en/innodb-parameters.html

5.6的新参数会在另一篇文章说明。

...

三 总结

上面大概介绍了5.6的一些新的特性,后续会不定时更新,尽量避免5.6的一些问题。

 

四 参考文章

http://wenku.baidu.com/link?url=QsvvYBaC8oicJSpcYlON8jXdKZ8X3EKxgvoRm5xcKoPXbp4qOajVNbIs9jP0N513RNFHIcn3NiMkGTHdfj4_9EjdmzT4u4s5tcU9NQAuBxW   推荐看看

http://dev.mysql.com/doc/refman/5.6/en/index.html

http://www.ttlsa.com/mysql/summary-of-the-new-features-of-mysql5_6/

http://blog.itpub.net/22664653/

http://mysqllover.com/?p=919

http://hidba.ga/2014/09/24/in-and-range/

posted @ 2015-09-07 17:38 jyzhou 阅读(...) 评论(...) 编辑 收藏