Hive入门到剖析(二)
5 Hive参数
hive.exec.max.created.files
说明:所有hive运行的map与reduce任务可以产生的文件的和
默认值:100000
hive.exec.dynamic.partition
说明:是否为自动分区
默认值:false
hive.mapred.reduce.tasks.speculative.execution
说明:是否打开推测执行
默认值:true
hive.input.format
说明:Hive默认的input format
默认值: org.apache.hadoop.hive.ql.io.CombineHiveInputFormat
如果有问题可以使用org.apache.hadoop.hive.ql.io.HiveInputFormat
hive.exec.counters.pull.interval
说明:Hive与JobTracker拉取counter信息的时间
默认值:1000ms
hive.script.recordreader
说明:使用脚本时默认的读取类
默认值: org.apache.hadoop.hive.ql.exec.TextRecordReader
hive.script.recordwriter
说明:使用脚本时默认的数据写入类
默认值: org.apache.hadoop.hive.ql.exec.TextRecordWriter
hive.mapjoin.check.memory.rows
说明: 内存里可以存储数据的行数
默认值: 100000
hive.mapjoin.smalltable.filesize
说明:输入小表的文件大小的阀值,如果小于该值,就采用普通的join
默认值: 25000000
hive.auto.convert.join
说明:是不是依据输入文件的大小,将Join转成普通的Map Join
默认值: false
hive.mapjoin.followby.gby.localtask.max.memory.usage
说明:map join做group by 操作时,可以使用多大的内存来存储数据,如果数据太大,则不会保存在内存里
默认值:0.55
hive.mapjoin.localtask.max.memory.usage
说明:本地任务可以使用内存的百分比
默认值: 0.90
hive.heartbeat.interval
说明:在进行MapJoin与过滤操作时,发送心跳的时间
默认值1000
hive.merge.size.per.task
说明: 合并后文件的大小
默认值: 256000000
hive.mergejob.maponly
说明: 在只有Map任务的时候 合并输出结果
默认值: true
hive.merge.mapredfiles
默认值: 在作业结束的时候是否合并小文件
说明: false
hive.merge.mapfiles
说明:Map-Only Job是否合并小文件
默认值:true
hive.hwi.listen.host
说明:Hive UI 默认的host
默认值:0.0.0.0
hive.hwi.listen.port
说明:Ui监听端口
默认值:9999
hive.exec.parallel.thread.number
说明:hive可以并行处理Job的线程数
默认值:8
hive.exec.parallel
说明:是否并行提交任务
默认值:false
hive.exec.compress.output
说明:输出使用压缩
默认值: false
hive.mapred.mode
说明: MapReduce的操作的限制模式,操作的运行在该模式下没有什么限制
默认值: nonstrict
hive.join.cache.size
说明: join操作时,可以存在内存里的条数
默认值: 25000
hive.mapjoin.cache.numrows
说明: mapjoin 存在内存里的数据量
默认值:25000
hive.join.emit.interval
说明: 有连接时Hive在输出前,缓存的时间
默认值: 1000
hive.optimize.groupby
说明:在做分组统计时,是否使用bucket table
默认值: true
hive.fileformat.check
说明:是否检测文件输入格式
默认值:true
hive.metastore.client.connect.retry.delay
说明: client 连接失败时,retry的时间间隔
默认值:1秒
hive.metastore.client.socket.timeout
说明: Client socket 的超时时间
默认值:20秒
mapred.reduce.tasks
默认值:-1
说明:每个任务reduce的默认值
-1 代表自动根据作业的情况来设置reduce的值
hive.exec.reducers.bytes.per.reducer
默认值: 1000000000 (1G)
说明:每个reduce的接受的数据量
如果送到reduce的数据为10G,那么将生成10个reduce任务
hive.exec.reducers.max
默认值:999
说明: reduce的最大个数
hive.exec.reducers.max
默认值:999
说明: reduce的最大个数
hive.metastore.warehouse.dir
默认值:/user/hive/warehouse
说明: 默认的数据库存放位置
hive.default.fileformat
默认值:TextFile
说明: 默认的fileformat
hive.map.aggr
默认值:true
说明: Map端聚合,相当于combiner
hive.exec.max.dynamic.partitions.pernode
默认值:100
说明:每个任务节点可以产生的最大的分区数
hive.exec.max.dynamic.partitions
默认值:1000
说明: 默认的可以创建的分区数
hive.metastore.server.max.threads
默认值:100000
说明: metastore默认的最大的处理线程数
hive.metastore.server.min.threads
默认值:200
说明: metastore默认的最小的处理线程数
6 Hive高级编程
6.1 产生背景
为了满足客户个性化的需求,Hive被设计成一个很开放的系统,很多内容都支持用户定制,包括:
文件格式:Text File,Sequence File
内存中的数据格式: Java Integer/String, Hadoop IntWritable/Text
用户提供的 map/reduce 脚本:不管什么语言,利用stdin/stdout 传输数据
1、用户自定义函数
虽然Hive提供了很多函数,但是有些还是难以满足我们的需求。因此Hive提供了自定义函数开发
自定义函数包括三种UDF、UADF、UDTF
UDF(User-Defined-Function)
UDAF(User- Defined Aggregation Funcation)
UDTF(User-DefinedTable-Generating Functions) 用来解决 输入一行输出多行(On-to-many maping) 的需求。
2、HIVE中使用定义的函数的三种方式
在HIVE会话中add 自定义函数的jar文件,然后创建function,继而使用函数
在进入HIVE会话之前先自动执行创建function,不用用户手工创建
把自定义的函数写到系统函数中,使之成为HIVE的一个默认函数,这样就不需要create temporary function。
6.2 UDF
UDF(User-Defined-Function):UDF函数可以直接应用于select语句,对查询结构做格式化处理后,再输出内容。
编写UDF函数的时候需要注意一下几点
A、自定义UDF需要继承org.apache.hadoop.hive.ql.UDF
B、需要实现evaluate函数
C、evaluate函数支持重载
D、UDF只能实现一进一出的操作,如果需要实现多进一出,则需要实现UDAF。
UDF用法代码示例
import org.apache.Hadoop.hive.ql.exec.UDF public class Helloword extends UDF{ public Stringevaluate(){ return"hello world!"; } public Stringevaluate(String str){ return"hello world: " + str; } }
开发步骤
开发代码
把程序打包放到目标机器上去
进入hive客户端
添加jar包:hive>add jar/run/jar/udf_test.jar;
创建临时函数:hive>CREATE TEMPORARY FUNCTION my_add AS'com.hive.udf.Add '
查询HQL语句:
SELECT my_add (8, 9) FROM scores;
SELECT my_add (scores.math, scores.art) FROM scores;
销毁临时函数:hive> DROP TEMPORARY FUNCTION my_add ;
细节
在使用UDF的时候,会自动进行类型转换,例如:
SELECT my_add (8,9.1) FROM scores;
结果是17.1,UDF将类型为Int的参数转化成double。类型的饮食转换是通过UDFResolver来进行控制的
6.3 UDAF
UDAF
Hive查询数据时,有些聚类函数在HQL没有自带,需要用户自定义实现
用户自定义聚合函数: Sum, Average…… n -1
UDAF(User- Defined Aggregation Funcation)
用法
一下两个包是必须的import org.apache.hadoop.hive.ql.exec.UDAF和org.apache.hadoop.hive.ql.exec.UDAFEvaluator
开发步骤
函数类需要继承UDAF类,内部类Evaluator实UDAFEvaluator接口
Evaluator需要实现 init、iterate、terminatePartial、merge、terminate这几个函数
a)init函数实现接口UDAFEvaluator的init函数。
b)iterate接收传入的参数,并进行内部的轮转。其返回类型为boolean。
c)terminatePartial无参数,其为iterate函数轮转结束后,返回轮转数据,terminatePartial类似于hadoop的Combiner。
d)merge接收terminatePartial的返回结果,进行数据merge操作,其返回类型为boolean。
e)terminate返回最终的聚集函数结果。
执行步骤
执行求平均数函数的步骤
a)将java文件编译成Avg_test.jar。
b)进入hive客户端添加jar包:
hive>add jar /run/jar/Avg_test.jar。
c)创建临时函数:
hive>create temporary function avg_test 'hive.udaf.Avg';
d)查询语句:
hive>select avg_test(scores.math) from scores;
e)销毁临时函数:
hive>drop temporary function avg_test;
UDAF代码示例
public class MyAvg extends UDAF { public static class AvgEvaluator implements UDAFEvaluator {}public void init() {}public boolean iterate(Double o) {}public AvgState terminatePartial() {}public boolean terminatePartial(Double o) { }public Double terminate() {} }
6.4 UDTF
UDTF:UDTF(User-Defined Table-GeneratingFunctions) 用来解决 输入一行输出多行(On-to-many maping) 的需求。
开发步骤
必须继承org.apache.Hadoop.hive.ql.udf.generic.GenericUDTF
实现initialize, process, close三个方法
UDTF首先会调用initialize方法,此方法返回UDTF的返回行的信息(返回个数,类型),初始化完成后,会调用process方法,对传入的参数进行处理,可以通过forword()方法把结果返回.
最后close()方法调用,对需要清理的方法进行清理
使用方法
UDTF有两种使用方法,一种直接放到select后面,一种和lateral view一起使用
直接select中使用:select explode_map(properties) as(col1,col2) from src;
不可以添加其他字段使用:select a, explode_map(properties) as (col1,col2) from src
不可以嵌套调用:select explode_map(explode_map(properties)) from src
不可以和group by/cluster by/distribute by/sort by一起使用:select explode_map(properties) as (col1,col2) from src group bycol1, col2
和lateral view一起使用:select src.id,mytable.col1, mytable.col2 from src lateral view explode_map(properties)mytable as col1, col2;
此方法更为方便日常使用。执行过程相当于单独执行了两次抽取,然后union到一个表里。
lateral view
语法:lateralView: LATERAL VIEW udtf(expression) tableAlias AScolumnAlias (',' columnAlias)* fromClause: FROM baseTable (lateralView)*
LateralView用于UDTF(user-defined table generating functions)中将行转成列,例如explode().
目前Lateral View不支持有上而下的优化。如果使用Where子句,查询可能将不被编译。
解决方法见:在查询之前执行set hive.optimize.ppd=false;
例子
pageAds。它有两个列
string pageid | Array |
" front_page" | [1, 2, 3] |
"contact_page " | [ 3, 4, 5] |
SELECT pageid, adid FROM pageAds LATERAL VIEWexplode(adid_list) adTable AS adid;
将输出如下结果
string pageid int adid
"front_page" 1
…….
"contact_page"3
代码示例
public class MyUDTF extends GenericUDTF{public StructObjectInspector initialize(ObjectInspector[] args) {}public void process(Object[] args) throws HiveException { }}
7 HiveQL
7.1 DDL
1、DDL功能
建表
删除表
修改表结构
创建/删除视图
创建数据库
显示命令
增加分区、删除分区
重命名表
修改列的名字、类型、位置、注释
增加/更新列
增加表的元数据信息
2、建表
CREATE [EXTERNAL] TABLE [IF NOT EXISTS] table_name [(col_namedata_type [COMMENT col_comment], ...)] [COMMENTtable_comment] [PARTITIONED BY(col_name data_type [COMMENT col_comment], ...)] [CLUSTERED BY(col_name, col_name, ...) [SORTED BY(col_name [ASC|DESC], ...)] INTO num_buckets BUCKETS] [ROW FORMATrow_format] [STORED ASfile_format] [LOCATIONhdfs_path]
CREATE TABLE 创建一个指定名字的表。如果相同名字的表已经存在,则抛出异常;用户可以用 IF NOT EXIST 选项来忽略这个异常
EXTERNAL 关键字可以让用户创建一个外部表,在建表的同时指定一个指向实际数据的路径(LOCATION)
LIKE 允许用户复制现有的表结构,但是不复制数据
COMMENT可以为表与字段增加描述
ROW FORMAT
DELIMITED[FIELDS TERMINATED BY char] [COLLECTION ITEMS TERMINATED BY char] [MAP KEYSTERMINATED BY char] [LINES TERMINATED BY char] | SERDEserde_name [WITH SERDEPROPERTIES (property_name=property_value,property_name=property_value, ...)]
用户在建表的时候可以自定义 SerDe 或者使用自带的 SerDe。如果没有指定 ROW FORMAT 或者 ROW FORMAT DELIMITED,将会使用自带的 SerDe。在建表的时候,用户还需要为表指定列,用户在指定表的列的同时也会指定自定义的SerDe,Hive 通过 SerDe 确定表的具体的列的数据。
STORED AS SEQUENCEFILE |TEXTFILE |RCFILE |INPUTFORMAT input_format_classname OUTPUTFORMAT output_format_classname
如果文件数据是纯文本,可以使用 STORED AS TEXTFILE。如果数据需要压缩,使用 STORED AS SEQUENCE 。
建立外部表
CREATE EXTERNAL TABLE page_view(viewTime INT, useridBIGINT, page_urlSTRING, referrer_url STRING, ip STRINGCOMMENT 'IP Address of the User', country STRINGCOMMENT 'country of origination') COMMENT 'This isthe staging page view table' ROW FORMATDELIMITED FIELDS TERMINATED BY '\054' STORED AS TEXTFILE LOCATION'';
建分区表
CREATE TABLE par_table(viewTime INT, userid BIGINT, page_urlSTRING, referrer_url STRING, ip STRINGCOMMENT 'IP Address of the User') COMMENT 'This isthe page view table' PARTITIONEDBY(date STRING, pos STRING)ROW FORMAT DELIMITED '\t' FIELDSTERMINATED BY '\n'STORED AS SEQUENCEFILE;
建Bucket表
CREATE TABLE par_table(viewTime INT, userid BIGINT, page_urlSTRING, referrer_url STRING, ip STRINGCOMMENT 'IP Address of the User') COMMENT 'This isthe page view table' PARTITIONEDBY(date STRING, pos STRING) CLUSTEREDBY(userid) SORTED BY(viewTime) INTO 32 BUCKETS ROW FORMAT DELIMITED'\t' FIELDSTERMINATED BY '\n'STORED AS SEQUENCEFILE;
复制一个空表
CREATE TABLE empty_key_value_storeLIKE key_value_store;
删除表
DROP TABLE table_name
增加、删除分区
增加
ALTER TABLE table_name ADD [IF NOT EXISTS] partition_spec[ LOCATION 'location1' ] partition_spec [ LOCATION 'location2' ] ... partition_spec: : PARTITION(partition_col = partition_col_value, partition_col = partiton_col_value, ...)
删除
ALTER TABLE table_name DROP partition_spec,partition_spec,...
重命名表
ALTER TABLE table_name RENAME TO new_table_name
修改列的名字、类型、位置、注释
ALTER TABLE table_name CHANGE [COLUMN] col_old_namecol_new_name column_type [COMMENT col_comment] [FIRST|AFTER column_name]
这个命令可以允许改变列名、数据类型、注释、列位置或者它们的任意组合
增加/更新列
ALTER TABLE table_name ADD|REPLACE COLUMNS (col_namedata_type [COMMENT col_comment], ...)
ADD是代表新增一字段,字段位置在所有列后面(partition列前)
REPLACE则是表示替换表中所有字段。
增加表的元数据信息
ALTER TABLE table_name SET TBLPROPERTIES table_propertiestable_properties: :[property_name = property_value…..]
用户可以用这个命令向表中增加metadata
改变表文件格式与组织
ALTER TABLE table_name SET FILEFORMAT file_format;ALTER TABLE table_name CLUSTERED BY(userid) SORTEDBY(viewTime) INTO num_buckets BUCKETS;
这个命令修改了表的物理存储属性
创建/删除视图
CREATE VIEW [IF NOT EXISTS] view_name [ (column_name[COMMENT column_comment], ...) ][COMMENT view_comment][TBLPROPERTIES(property_name = property_value, ...)] AS SELECT
增加视图
如果没有提供表名,视图列的名字将由定义的SELECT表达式自动生成
如果修改基本表的属性,视图中不会体现,无效查询将会失败
视图是只读的,不能用LOAD/INSERT/ALTER
DROP VIEW view_name
删除视图
创建数据库
CREATE DATABASE name
显示命令
show tables;show databases;show partitions ;show functionsdescribe extended table_name dot col_name
7.2 DML
1、DML功能
向数据表内加载文件
将查询结果插入到Hive表中
0.8新特性 insert into
2、向数据表内加载文件
LOAD DATA [LOCAL] INPATH 'filepath' [OVERWRITE] INTOTABLE tablename [PARTITION (partcol1=val1, partcol2=val2 ...)]
Load 操作只是单纯的复制/移动操作,将数据文件移动到 Hive 表对应的位置。
filepath
相对路径,例如:project/data1
绝对路径,例如: /user/hive/project/data1
包含模式的完整 URI,例如:
hdfs://namenode:9000/user/hive/project/data1
3、向数据表内加载文件
加载的目标可以是一个表或者分区。如果表包含分区,必须指定每一个分区的分区名
filepath 可以引用一个文件(这种情况下,Hive 会将文件移动到表所对应的目录中)或者是一个目录(在这种情况下,Hive 会将目录中的所有文件移动至表所对应的目录中)
4、LOCAL关键字
指定了LOCAL
load 命令会去查找本地文件系统中的 filepath。如果发现是相对路径,则路径会被解释为相对于当前用户的当前路径。用户也可以为本地文件指定一个完整的 URI,比如:file:///user/hive/project/data1.
load 命令会将 filepath 中的文件复制到目标文件系统中。目标文件系统由表的位置属性决定。被复制的数据文件移动到表的数据对应的位置
没有指定LOCAL
如果 filepath 指向的是一个完整的 URI,hive 会直接使用这个 URI。 否则
如果没有指定 schema 或者 authority,Hive 会使用在 hadoop 配置文件中定义的 schema 和 authority,fs.default.name指定了 Namenode 的 URI
如果路径不是绝对的,Hive 相对于 /user/ 进行解释。 Hive 会将 filepath 中指定的文件内容移动到 table (或者 partition)所指定的路径中
5、OVERWRITE
指定了OVERWRITE
目标表(或者分区)中的内容(如果有)会被删除,然后再将 filepath 指向的文件/目录中的内容添加到表/分区中。
如果目标表(分区)已经有一个文件,并且文件名和 filepath 中的文件名冲突,那么现有的文件会被新文件所替代。
6、将查询结果插入Hive表
将查询结果插入Hive表
将查询结果写入HDFS文件系统
基本模式
INSERT OVERWRITE TABLE tablename1 [PARTITION(partcol1=val1, partcol2=val2 ...)] select_statement1 FROM from_statement
多插入模式
FROM from_statementINSERT OVERWRITE TABLE tablename1 [PARTITION(partcol1=val1, partcol2=val2 ...)] select_statement1[INSERT OVERWRITE TABLE tablename2 [PARTITION ...]select_statement2] ...
自动分区模式
INSERT OVERWRITETABLE tablename PARTITION (partcol1[=val1], partcol2[=val2] ...)select_statement FROM from_statement
7、将查询结果写入HDFS文件系统
INSERT OVERWRITE [LOCAL] DIRECTORY directory1 SELECT ...FROM ... FROMfrom_statement INSERTOVERWRITE [LOCAL] DIRECTORY directory1 select_statement1 [INSERTOVERWRITE [LOCAL] DIRECTORY directory2 select_statement2]
数据写入文件系统时进行文本序列化,且每列用^A 来区分,\n换行
8、INSERT INTO
INSERT INTO TABLEtablename1 [PARTITION (partcol1=val1, partcol2=val2 ...)] select_statement1FROM from_statement
7.3 HiveQL 查询操作
1、SQL操作
基本的Select 操作
基于Partition的查询
Join
2、基本的Select 操作
SELECT [ALL | DISTINCT] select_expr, select_expr, ...FROM table_reference[WHERE where_condition][GROUP BY col_list [HAVING condition]][ CLUSTER BYcol_list | [DISTRIBUTE BYcol_list] [SORT BY| ORDER BY col_list]][LIMIT number]
使用ALL和DISTINCT选项区分对重复记录的处理。默认是ALL,表示查询所有记录。DISTINCT表示去掉重复的记录
Where 条件
类似我们传统SQL的where 条件
目前支持 AND,OR ,0.9版本支持between
IN, NOT IN
不支持EXIST ,NOT EXIST
ORDER BY与SORT BY的不同
ORDER BY 全局排序,只有一个Reduce任务
SORT BY 只在本机做排序
3、Limit
Limit 可以限制查询的记录数
SELECT * FROM t1 LIMIT 5
实现Top k 查询
下面的查询语句查询销售记录最大的 5 个销售代表。
SET mapred.reduce.tasks = 1
SELECT * FROMtest SORT BY amount DESC LIMIT 5
REGEX Column Specification
SELECT 语句可以使用正则表达式做列选择,下面的语句查询除了 ds 和 hr 之外的所有列:
SELECT `(ds|hr)?+.+` FROM test
基于Partition的查询
一般 SELECT 查询会扫描整个表,使用PARTITIONED BY 子句建表,查询就可以利用分区剪枝(input pruning)的特性
Hive 当前的实现是,只有分区断言出现在离 FROM 子句最近的那个WHERE 子句中,才会启用分区剪枝
4、Join
Syntaxjoin_table: table_referenceJOIN table_factor [join_condition] | table_reference{LEFT|RIGHT|FULL} [OUTER] JOIN table_reference join_condition | table_referenceLEFT SEMI JOIN table_reference join_condition table_reference: table_factor | join_table table_factor: tbl_name[alias] | table_subqueryalias | (table_references ) join_condition: ONequality__expression ( AND equality_expression )* equality_expression: expression =expression
Hive 只支持等值连接(equality joins)、外连接(outer joins)和(left semi joins)。Hive 不支持所有非等值的连接,因为非等值连接非常难转化到 map/reduce 任务
LEFT,RIGHT和FULL OUTER关键字用于处理join中空记录的情况
LEFT SEMI JOIN 是 IN/EXISTS 子查询的一种更高效的实现
join 时,每次 map/reduce 任务的逻辑是这样的:reducer 会缓存 join 序列中除了最后一个表的所有表的记录,再通过最后一个表将结果序列化到文件系统
实践中,应该把最大的那个表写在最后
5、join 查询时,需要注意几个关键点
只支持等值join
SELECT a.* FROM a JOIN b ON (a.id = b.id)SELECT a.* FROM a JOIN b ON (a.id = b.idAND a.department = b.department)
可以 join 多于 2 个表,例如:
SELECT a.val,b.val, c.val FROM a JOIN b ON (a.key =b.key1) JOIN c ON (c.key = b.key2)
如果join中多个表的 join key 是同一个,则 join 会被转化为单个 map/reduce 任务
LEFT,RIGHT和FULL OUTER
例子:SELECT a.val, b.val FROM a LEFT OUTER JOIN b ON (a.key=b.key)
如果你想限制 join 的输出,应该在 WHERE 子句中写过滤条件--或是在 join 子句中写。
容易混淆的问题是表分区的情况
SELECT c.val, d.val FROM c LEFT OUTER JOIN d ON(c.key=d.key)
WHEREa.ds='2010-07-07' AND b.ds='2010-07-07'
如果 d 表中找不到对应 c 表的记录,d 表的所有列都会列出 NULL,包括ds 列。也就是说,join 会过滤 d 表中不能找到匹配 c 表 join key 的所有记录。这样的话,LEFT OUTER 就使得查询结果与 WHERE 子句无关
解决办法
SELECT c.val, d.val FROM c LEFT OUTER JOIN d ON (c.key=d.keyAND d.ds='2009-07-07' AND c.ds='2009-07-07')
LEFT SEMI JOIN
LEFT SEMI JOIN 的限制是, JOIN 子句中右边的表只能在 ON 子句中设置过滤条件,在 WHERE 子句、SELECT 子句或其他地方过滤都不行。
SELECT a.key, a.value FROM a WHERE a.key in (SELECT b.key FROM B); 可以被重写为: SELECT a.key,a.val FROM a LEFT SEMIJOIN b on (a.key = b.key)
UNION ALL
用来合并多个select的查询结果,需要保证select中字段须一致
select_statement UNION ALL select_statement UNION ALLselect_statement ...
7.4 从SQL到HiveQL应该转变的几个习惯
1、Hive不支持等值连接
SQL中对两表内联可以写成:
select * from dual a,dual b where a.key = b.key;
Hive中应为
select * from dual a join dual b on a.key = b.key;
2、分号字符
分号是SQL语句结束标记,在HiveQL中也是,但是在HiveQL中,对分号的识别没有那么智慧,例如:
select concat(key,concat(';',key)) from dual;
但HiveQL在解析语句时提示:
FAILED:Parse Error: line 0:-1 mismatched input '
解决的办法是,使用分号的八进制的ASCII码进行转义,那么上述语句应写成:
select concat(key,concat('\073',key)) from dual;
3、IS [NOT]NULL
SQL中null代表空值, 值得警惕的是, 在HiveQL中String类型的字段若是空(empty)字符串, 即长度为0, 那么对它进行ISNULL的判断结果是False。