美文网首页数据传送格式
基于Mysql7.0 JSON格式IP抓取数据存储查询设计

基于Mysql7.0 JSON格式IP抓取数据存储查询设计

作者: 莫逐 | 来源:发表于2017-05-12 10:26 被阅读123次

    需求

    抓取网站多样

    抓取元素不同

    查询条件不固定

    Mysql JSON介绍

    JSON的格式非常简单:名称/键值。之前MySQL版本里面要实现这样的存储,要么用VARCHAR要么用TEXT大文本。 MySQL5.7发布后,专门设计了JSON数据类型以及关于这种类型的检索以及其他函数解析。 为什么JSON的原生支持

    文档合法性

    在MySQL5.7.7对JSON提供原生类型的支持之前,用户可以用TEXT或者BLOB类型来存储JSON文档。但对于MySQL来说,用户插入的数据只是序列化后的一个普通的字符串,不会对JSON文档本身的语法合法性做检查,文档的合法性需要用户自己保证。在引入新的JSON类型之后,插入语法错误的JSON文档,MySQL会提示错误,并在插入之后做归一化处理,保证每一个键对应一个值。

    更有效的访问

    MySQL 5.7.7+本身提供了很多原生的函数以及路径表达式来方便用户访问JSON数据。例如对于下面的JSON文档: { "a": [ [ 3, 2 ], [ { "c" : "d" }, 1 ] ], "b": { "c" : 6 }, "one potato": 7, "b.c" : 8 }

    用户可以使用 $.a[1][0]获取{ "c" : "d" }, $.a[1]获取[ { "c" : "d" }, 1 ] 还可以使用通配符 * 和 ** 来进行模糊匹配,详见下一段。

    性能优化

    在MySQL提供JSON原生支持之前,如果用户需要获取或者修改某个JSON文档的键值,需要把TEXT或者BLOB整个字符串读出来反序列化成JSON对象,然后通过各种库函数访问JSON数据。显然这样是非常没有效率的,特别是对较大的文档。而原生JSON的性能,特别是读性能非常好。根据Oracle公司针对200K+数据文档做的性能测试表明,同样的数据用TEXT和JSON类型的查询性能差异达到两个数量级以上,而且用户还可以对经常访问的JSON键值做索引,进一步提升性能。JSON数据操作性能的提升是基于JSON数据本身的存储结构的,下文会进一步介绍。

    JSON的操作接口及路径表达式

    JSON的操作接口

    JSON_APPEND()JSON_ARRAY_INSERT()JSON_UNQUOTE()JSON_ARRAY()JSON_REPLACE()JSON_CONTAINS()JSON_DEPTH()JSON_EXTRACT()JSON_INSERT()JSON_KEYS()JSON_LENGTH()JSON_VALID()JSON_MERGE()JSON_OBJECT()JSON_QUOTE()JSON_REMOVE()JSON_CONTAINS_PATH()JSON_SEARCH()JSON_SET()JSON_TYPE()

    JSON路径表达式

    为了更方便快速的访问JSON的键值,MySQL 5.7.7+提供了新的路径表达式语法支持。前文提到的$.a[1][0]就是路径表达式的一个具体的示例。完整的路径表达式语法为:

    pathExpression>::= scope  [ ( pathLeg )* ]scope::= [ columnReference ] dollarSigncolumnReference::= [ [ databaseIdentifier period  ] tableIdentifier period ] columnIdentifierdatabaseIdentifier::= sqlIdentifiertableIdentifier::= sqlIdentifiercolumnIdentifier::= sqlIdentifierpathLeg::= member | arrayLocation | doubleAsteriskmember::= period ( keyName | asterisk )arrayLocation::= leftBracket ( non-negative-integer | asterisk ) rightBracketkeyName::= ECMAScript-identifier | double-quoted-string-literaldoubleAsterisk::= **

    还是以

    { "a":[ [3,2], [ { "c" :"d"},1] ], "b":{ "c" :6}, "one potato":7, "b.c" :8}

    $.a[1] 获取的值为 [ { "c" : "d" }, 1 ] $.b.c 获取的值为 6 $."b.c" 获取的值为 8

    JSON的存储结构及具体实现

    在处理JSON时,MySQL使用的utf8mb4字符集,utf8mb4是utf8和ascii的超集。由于历史原因,这里utf8并非是我们常说的UTF-8 Unicode变长编码方案,而是MySQL自身定义的utf8编码方案,最长为三个字节。具体区别非本文重点,请大家自行Google了解。

    MySQL在内存中是以DOM的形式表示JSON文档,而且在MySQL解析某个具体的路径表达式时,只需要反序列化和解析路径上的对象,而且速度极快。要弄清楚MySQL是如何做到这些的,我们就需要了解JSON在硬盘上的存储结构。有个有趣的点是,JSON对象是BLOB的子类,在其基础上做了特化。

    JSON的索引

    现在MySQL不支持对JSON列进行索引,官网文档的说明是:

    JSON columns cannot be indexed. You can work around this restriction by creating an index on a generated column that extracts a scalar value from the JSON column.

    虽然不支持直接在JSON列上建索引,但MySQL规定,可以首先使用路径表达式对JSON文档中的标量值建立虚拟列,然后在虚拟列上建立索引。这样用户可以使用表达式对自己感兴趣的键值建立索引。举个具体的例子来说明:

    CREATETABLEfeatures (idINTNOTNULLAUTO_INCREMENT, featureJSONNOTNULL, PRIMARYKEY(id));

    插入它的JSON数据的格式为:

    {  "type":"Feature",  "properties":{      "TO_ST":"0",      "BLKLOT":"0001001",      "STREET":"UNKNOWN",      "FROM_ST":"0",      "LOT_NUM":"001",      "ST_TYPE":null,      "ODD_EVEN":"E",      "BLOCK_NUM":"0001",      "MAPBLKLOT":"0001001"}}

    使用:

    ALTERTABLEfeaturesADDfeature_streetVARCHAR(30)AS(JSON_UNQUOTE(feature->"$.properties.STREET"));ALTERTABLEfeaturesADDINDEX(feature_street);

    两个步骤,可以对feature列中properties键值下的STREET键(feature->"$.properties.STREET")创建索引。

    其中,feature_street列就是新添加的虚拟列。之所以取名虚拟列,是因为与它对应的还有一个存储列(stored column)。它们最大的区别为虚拟列只修改数据库的metadata,并不会存储真实的数据在硬盘上,读取过程也是实时计算的方式;而存储列会把表达式的列存储在硬盘上。两者使用的场景不一样,默认情况下通过表达式生成的列为虚拟列。

    这样虚拟列的添加和删除都会非常快,而在虚拟列上建立索引跟传统的建立索引的方式并没有区别,会提高虚拟列读取的性能,减慢整体插入的性能。虚拟列的特性结合JSON的路径表达式,可以方便的为用户提供高效的键值索引功能。

    JSON比较与排序

    JSON值可以使用=, <, <=, >, >=, <>, !=, <=>等操作符,BETWEEN, IN,GREATEST, LEAST等操作符现在还不支持。JSON值使用的两级排序规则,第一级基于JSON的类型,类型不同的使用每个类型特有的排序规则。

    实践

    创建表

    CREATETABLE`user`(`uid`int(11)NOTNULLAUTO_INCREMENT,`info`jsonDEFAULTNULL, #注意desc字段类型为jsonPRIMARYKEY(`uid`))ENGINE=InnoDBAUTO_INCREMENT=3DEFAULTCHARSET=utf8;

    插入数据

    INSERT  INTO `user`(`uid`,`info`) VALUES (1,'{\"mail\":\"jiangchengyao@gmail.com\",\"name\":\"David\",\"address\":\"Shangahai\"}'),(2,'{\"mail\":\"amy@gmail.com\",\"name\":\"Amy\"}');

    普通查询

    selectuid,infofromuserwhereinfolike'%mail%'

    key查询

    SELECTuid,json_keys(info)as"keys"FROMuser;

    value查询

    SELECTuid,json_extract(info,'$.mail')AS'mail',json_extract(info,'$.name')AS'name'FROMUSER;

    条件查询

    json表里面有个content字段数据如下面的Json格式 {"eq":{"f_1360040399":"admin","f_3038116851":"20","f_318208994":"admin@localhost.com"}}

    需要对其中的f开头的Json key值所对的value进行模糊查询,方法如下:

    select * from table where content->'$.eq.f_1360040399' like '%min%'

    SELECTuid,json_extract(info,'$.mail')AS'mail',json_extract(info,'$.name')AS'name'FROMUSERwhereinfo->'$.name'="Amy";

    SELECTuid,json_extract(info,'$.mail')AS'mail',json_extract(info,'$.name')AS'name'FROMUSERwhereinfo->'$.name'like"%A%";

    性能测试

    100万条数据检索

    创建随机字符串

    select concat('{\"mail\":\"',rand_string(15),'@gmail.com\",\"name\":\"',rand_string(20),'\",\"address\":\"',rand_string(30),'\",\"company\":\"',rand_string(30),'\"}') from dual ;

    造100万数据

    DELIMITER $$USE `platform`$$DROPPROCEDUREIFEXISTS`autoinsert`$$CREATEPROCEDURE`autoinsert`(INIP_NUM INT)BEGINDECLAREiIPINTDEFAULT0 ;DECLARE json varchar(255);WHILE(iIP < IP_NUM)DOSETjson =concat('{\"mail\": \"',rand_string(15),'@gmail.com\", \"name\":\"',rand_string(20),'\", \"address\": \"',rand_string(30),'\", \"company\": \"',rand_string(30),'\"}');  insertintouser(info)values (json);SETiIP = iIP+1;ENDWHILE;END$$DELIMITER ;call `autoinsert`(1000000);

    条件查询

    SELECTuid,json_extract(info,'$.mail')AS'mail',json_extract(info,'$.name')AS'name'FROMUSERwhereinfo->'$.name'like"%D%";

    创建索引

    ALTERTABLEuserADDnameVARCHAR(50)AS(JSON_UNQUOTE(info->"$.name"));ALTERTABLEuserADDINDEX(name);

    条件查询(同上)

    DurationTime统计表

    数据量未加索引添加索引

    110万0.005s0.005s

    09:58:36SELECT uid,json_extract(info,'$.mail')AS'mail',json_extract(info,'$.name')AS'name'FROM USER where info->'$.name'like"%D%"277070row(s)returned0.005sec /2.416sec10:09:05SELECT uid,json_extract(info,'$.mail')AS'mail',json_extract(info,'$.name')AS'name'FROM USER where info->'$.name'like"%A%"276982row(s)returned0.002sec /1.858sec

    结论

    本文介绍了Mysql JSON数据类型的特性,并实践测试JSON效率。

    Json格式的灵活性满足需求中

    抓取网站多样

    不同网站对应不同json格式

    抓取元素不同

    查询条件不固定

    查询条件可以先将json_keys(json)返回前端动态生成查询条件

    100万数据量添加索引效果不明显 ,大数据量待测试

    参考资料

    生成 JSON 值的函数

    MySQL5.7 JSON实现简介

    MySQL 5.7 新特性 JSON 的创建,插入,查询,更新

    mysql存储过程中(varchar)变量的问题

    mysql concat 字符串拼接命令

    关于mysql的Fetch Time 和 Duration Time

    mysql 索引( mysql index )

    相关文章

      网友评论

        本文标题:基于Mysql7.0 JSON格式IP抓取数据存储查询设计

        本文链接:https://www.haomeiwen.com/subject/nbdztxtx.html