是比较偏的部分,可能是因为不是标准SQL,生产环境或者线上很少看到有人使用。本文以‘小白’视角,记录下如何使用HANDLER,以及相关的代码简单介绍。
本文的源码部分基于5.7.5,从5.7开始也支持对分区表的HANDLER操作了。
1.使用
具体的参考官方文档: http://dev.mysql.com/doc/refman/5.7/en/handler.html
我们以一个简单的测试表为例:
create table t1 (c1 int primary key, c2 int, c3 int, key (c2,c3));
insert into t1 values (1,2,3),(2,3,4),(3,4,5),(4,5,6),(5,6,7),(6,9,10),(7,8,10);
mysql> show create table t1\G
*************************** 1. row ***************************
Table: t1
Create Table: CREATE TABLE `t1` (
`c1` int(11) NOT NULL,
`c2` int(11) DEFAULT NULL,
`c3` int(11) DEFAULT NULL,
PRIMARY KEY (`c1`),
KEY `c2` (`c2`,`c3`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
打开表
语法:HANDLER tbl_name OPEN [ [AS] alias]
HANDLER…OPEN语句用于打开一张表,这样就可以随后通过HANDLER…READ语法来读取记录,表对象存储在session中,不与别的线程共享。
mysql> handler t1 open;
Query OK, 0 rows affected (0.00 sec)
然后就可以从表上读取数据了:
HANDLER tbl_name READ { FIRST | NEXT } [ WHERE where_condition ] [LIMIT … ]
HANDLER t1 READ 操作一直在session里保持读到的索引位置,READ FIRST 会重置到第一条记录,READ NEXT会依次读取下一条记录,顺序是按照指定的索引顺序,你也可以指定where条件来进行过滤,注意如果不指定LIMIT那就只会返回一条记录。
举几个例子:
mysql> handler t1 read first;
+—-+——+——+
| c1 | c2 | c3 |
+—-+——+——+
| 1 | 2 | 3 |
+—-+——+——+
1 row in set (0.00 sec)mysql> handler t1 read next;
+—-+——+——+
| c1 | c2 | c3 |
+—-+——+——+
| 2 | 3 | 4 |
+—-+——+——+
1 row in set (0.00 sec)
mysql> handler t1 read first where c2 > 3 limit 3;
+—-+——+——+
| c1 | c2 | c3 |
+—-+——+——+
| 3 | 4 | 5 |
| 4 | 5 | 6 |
| 5 | 6 | 7 |
+—-+——+——+
3 rows in set (0.00 sec)
指定索引读数据
语法:HANDLER tbl_name READ index_name { FIRST | NEXT | PREV | LAST } [ WHERE where_condition ] [LIMIT … ]
mysql> handler t1 read c2 first;
+—-+——+——+
| c1 | c2 | c3 |
+—-+——+——+
| 1 | 2 | 3 |
+—-+——+——+
1 row in set (0.00 sec)mysql> handler t1 read c2 first where c2 >7;
+—-+——+——+
| c1 | c2 | c3 |
+—-+——+——+
| 7 | 8 | 10 |
+—-+——+——+
1 row in set (0.00 sec)
指定二级索引数据读取条件:
语法:HANDLER tbl_name READ index_name { = | <= | >= | < | > } (value1,value2,…) [ WHERE where_condition ] [LIMIT … ]
使用主键读
mysql> handler t1 read `PRIMARY` = (3);
+—-+——+——+
| c1 | c2 | c3 |
+—-+——+——+
| 3 | 4 | 5 |
+—-+——+——+
1 row in set (0.00 sec)
mysql> handler t1 read c2 first where c2 > 7 limit 10;
+—-+——+——+
| c1 | c2 | c3 |
+—-+——+——+
| 7 | 8 | 10 |
| 6 | 9 | 10 |
+—-+——+——+
2 rows in set (0.00 sec)使用二级索引读
mysql> handler t1 read c2 first where c2 =7 limit 2;
Empty set (0.00 sec)mysql> handler t1 read c2 first where c2 =8;
+—-+——+——+
| c1 | c2 | c3 |
+—-+——+——+
| 7 | 8 | 10 |
+—-+——+——+
1 row in set (0.00 sec)
如果二级索引有多个列的话,也可以指定少于列数的索引键值。
关闭HANDLER
HANDLER tbl_name CLOSE
总的来说,操作还是比较简单的,HANDLER接口提供了一种直达引擎层读取数据的方法。不过HANDLER接口只提供了读取数据的接口,并没有提供DML接口。
如果OPEN了一个表,对这张表做DDL就会被阻塞住,直到显式执行HANDLER..CLOSE掉才能执行。
另外从语法定义来看,只能返回全行,而不能选择返回特定的列值。
根据文档描述,HANDLER接口相对SQL接口,其优点在于更少的语法解析,SQL检查和优化器开销,只需要显式打开一次表;对于Innodb, HANDLER接口总是不加Innodb表锁,可以读取UNCOMMIT的数据。
2.代码实现
从上面的描述可以知道,HANDLER无非就对应三种操作:OPEN, READ, CLOSE,而在内部实现中,也对应三个类接口:
Sql_cmd_handler_open
Sql_cmd_handler_read
Sql_cmd_handler_close
相关的代码实现都集中在sql_handler.cc文件中。
我们依次展开介绍
a.打开表
主函数:Sql_cmd_handler_open::execute
在语法解析时,就实例了一个Sql_cmd_handler_open对象,执行阶段调用Sql_cmd_handler_open::execute函数
每通过HANDLER打开一张表,都存储在thd->handler_tables_hash 哈希中,键值为表名,值为TABLE_LIST。
首先尝试打开同名的临时表(open_temporary_tables),如果没有的话,则检查权限并打开实体表(mysql_ha_open_table),打开的表的MDL锁类型为MDL_EXPLICIT,表示需要显式释放(这里通过HANDLER..CLOSE)。
b.读取数据
主函数:Sql_cmd_handler_read::execute
从表上读取数据的实现也比较简单,从thd->handler_tables_hash中拿到已经打开的表对象,加上MDL_TRANSACTION类型的MDL_SHARED_READ MDL锁
总是读取整行记录:
hash_tables->table->read_set= &hash_tables->table->s->all_set
设置where条件,检查指定的索引是否存在,设置返回结果集的Meta,随后调用引擎的接口函数做初始化工作。
对应Innodb的函数为:ha_innobase::init_table_handle_for_HANDLER, 主要工作包括:1.开启一个只读事务;2.分配Readview;3. 注册XA(innobase_register_trx,感觉有点多余)
随后根据命令类型HANDLER…READ FIRST/NEXT/PREV/LAST 调用引擎接口来获取数据。
这坨代码可以帮助很好的理解引擎层和Server层如何进行数据检索。非常值得一读
c.关闭HANDLER
主函数:Sql_cmd_handler_close::execute
关闭已经打开的表(mysql_ha_close_table),释放MDL,并从thd->handler_tables_hash中移除