一文带你了解不一样的SQL,惊喜多多

SQL 由 IBM 于上世纪 70 年代创建,如今已经成为了使用最广泛的数据库查询语言。不过,相信很多人对于 SQL 的理解就是关系数据库,就是增删改查;实际上,SQL 在经历了四十而不惑之后就像“姐姐们”一样成熟而有魅力,同时它又敢于在不断变化的产业需求和各种非关系模型的冲击之下实现自我突破。因此,本文就给大家介绍一下最近几年 SQL 如何在各个领域乘风破浪!

images.jianshu.io/upload_images/15316292-c39c173b2c0ffa46.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

如果你认为 SQL 就是简单的增删改查(INSERT、SELECT、UPDATE、DELETE),那么你了解的仅仅是 1992 年的SQL。

如果你了解通用表表达式(CTE)和递归查询、用户定义类型或者 OLAP 功能,那么你使用的是 1999 年的 SQL。

如果你接触过窗口函数(分析函数)、MERGE(UPSERT)语句或者 XML 数据类型,应该知道这些不过是 2003 年的 SQL。

2006 年的 SQL 已经定义了 SQL 操作 XML 的规范,支持使用 XQuery 同时访问 SQL 数据和 XML 文档。2008 年又增加了 TRUNCATE TABLE 语句、INSTEAD OF 触发器以及 FETCH 子句等功能。

2011 年 SQL 最主要的新功能之一就是增强了对时态数据库(Temporal database)的支持,可以用于记录那些随着时间而变化的历史数据值,应用领域包括金融、保险、预订系统、医疗信息管理系统等。目前,MariaDB、Oracle、PostgreSQL、Microsoft SQL Server 在一定程度上实现了某些时态表功能,国内的腾讯 TDSQL 是一个全时态数据库系统。

时间来到了 2016 年,SQL 标准又增加了几个重要的功能,首先就是对 JSON 文档的支持。

一.SQL 与文档数据库

文档数据库属于 NoSQL 的一种,具有模式自由的存储特性,通常采用 JSON 格式进程数据的存储。常用的文档数据库包括 MongoDB、CouchDB 等。

实际上,2016 年 SQL 标准就已经增加了 JSON 功能的支持,包括:

  • JSON 对象的存储与检索;
  • 将 JSON 对象表示成 SQL 数据;
  • 将 SQL 数据表示成 JSON 对象。

这些功能可以表示为以下示意图:

image.png

如今,主流的关系数据库也都增加了原生 JSON 数据类型和相关函数的支持,包括 Oracle、MySQL、SQL Server、PostgreSQL 等。

image.png

我们以 MySQL 为例,演示一下如何使用 SQL 查询 JSON 数据。

select emp_id,
       emp_info, emp_info - > '$.emp_name' emp_name,
       emp_info - > '$.sex' sex,
       emp_info - >> '$.income[0].salary' salary
from employee_jsonlimit 3;
emp_id | emp_info | emp_name | sex | salary | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -- - | ------ - | 1 | { "sex" : "男", "email" : "liubei@shuguo.com", "income" : [{ "salary" : 33000.0 }, { "bonus" : 10000 }], "job_id" : 1, "dept_id" : 1, "manager" : null, "emp_name" : "刘备", "hire_date" : "2000-01-01" } | "刘备" | "男" | 33000.0 |
2 | { "sex" : "男", "email" : "guanyu@shuguo.com", "income" : [{ "salary" : 26000 }, { "bonus" : 10000 }], "job_id" : 2, "dept_id" : 1, "manager" : 1, "emp_name" : "关羽", "hire_date" : "2000-01-01" } | "关羽" | "男" | 26000 |
3 | { "sex" : "男", "email" : "zhangfei@shuguo.com", "income" : [{ "salary" : 24000 }, { "bonus" : 10000 }], "job_id" : 2, "dept_id" : 1, "manager" : 1, "emp_name" : "张飞", "hire_date" : "2000-01-01" } | "张飞" | "男" | 24000 |

其中,emp_info 字段类型为 JSON;-> 操作符返回的类型是 JSON,->> 返回的类型是字符串,使用 SQL/JSON 路径表达式获取数据中的元素值;代表整个文档;.emp_name 表示获取 JSON 对象的 emp_name 元素;$.income[0].salary 表示获取 income 数组中的第一个对象的 salary 元素,数组的下标从 0 开始。

除此之外,我们也可以将 JSON 数据转换为关系型数据。例如:

select emp_id, jt.*
from employee_json, json_table( emp_info, '$'
                columns( emp_name varchar( 50 ) path '$.emp_name',
                     sex varchar( 10 ) path '$.sex',
                     dept_id integer path '$.dept_id',
                     manager integer path '$.manager',
                     hire_date date path '$.hire_date',
                     job_id integer path '$.job_id',
                     salary integer path '$.income[0].salary',
                     bonus integer path '$.income[1].bonus',
                     email varchar( 100 ) path '$.email' )
                ) jtlimit 3;
emp_id | emp_name | sex | dept_id | manager | hire_date | job_id | salary | bonus | email |
------ | ---------- | -- - | ------ - | ------ - | ---------- | ------ | ------ | ---- - | ------------------ - |
1 | 刘 备 | 男 | 1 |       | 2000 - 01 - 01 | 1 | 33000 | 10000 | liubei@shuguo.com |
2 | 关羽 | 男 | 1 | 1 | 2000 - 01 - 01 | 2 | 26000 | 10000 | guanyu@shuguo.com |
3 | 飞 | 男 | 1 | 1 | 2000 - 01 - 01 | 2 | 24000 | 10000 | zhangfei@shuguo.com |

其中,$ 表示将整个 emp_info 作为数据行的来源;COLUMNS 定义了字段类型及其数据的来源,PATH 同样使用 SQL/JSON 路径表达式。

反之,我们也可以通过 SQL 函数将关系型数据转换为 JSON 数据。例如:

select json_object( 'emp_name', emp_name,
            'sex', sex,
            'income', json_array( json_object( 'salary', salary ), json_object( 'bonus', bonus ) )
            ) AS jo
from employee
limit 3;


jo | ------------------------------------------------------------------------------------ - | { "sex" : "男", "income" : [{ "salary" : 30000.00 }, { "bonus" : 10000.00 }], "emp_name" : "刘备" }
{ "sex" : "男", "income" : [{ "salary" : 26000.00 }, { "bonus" : 10000.00 }], "emp_name" : "关羽" }
{ "sex" : "男", "income" : [{ "salary" : 24000.00 }, { "bonus" : 10000.00 }], "emp_name" : "张飞" } |

其中, JSON_OBJECT 和 JSON_ARRAY 函数可以将表中的数据构造成 JSON 对象和数组。

不仅如此,使用 SQL 语句也可以对 JSON 节点数据进行 DML 操作,不再介绍具体案例。关于 MySQL 文档存储的详细介绍可以参考这篇文章。

总之,关系数据库对于 JSON 数据类型的支持可以方便我们将 SQL 的强大功能与 NoSQL 的灵活性相结合;当我们需要为应用增加文档数据支持的时候,除了使用专门的 NoSQL 数据库之外,也可以考虑直接在现有的数据库中使用 JSON 数据类型。

2016 年 SQL 标准增加的另一个重要的功能就是行模式识别(Row Pattern Recognition)。

二.SQL 与复杂事件处理

SQL 行模式识别使用 MATCH_RECOGNIZE 子句表示,通过指定一个模式(正则表达式)查找多行数据之间的规律,并且可以对这些匹配的数据进行过滤、分组和聚合操作。行模式识别可以用于分析各种时间序列数据,例如股票行情数据分析、金融欺诈检测或者系统事件日志分析等。

image.png

目前只有 Oracle 12c 实现了该功能,我们可以使用以下语句找出股票曲线中的所有 V 型曲线:

--Oracle 12c 实现
SELECT *
FROM stock MATCH_RECOGNIZE(
    PARTITION BY scode
    ORDER BY tradedate
    MEASURES STRT.tradedate AS start_date,
    LAST(DOWN.tradedate) AS bottom_date,
    LAST(UP.tradedate) AS end_date
    ONE ROW PER MATCH
    AFTER MATCH SKIP TO LAST UP
    PATTERN (STRT DOWN + UP +)
    DEFINE
    DOWN AS DOWN.price < PREV( DOWN.price ),
                 UP AS UP.price > PREV( UP.price )
    ) MR
ORDER BY MR.scode, MR.start_date;

其中,MATCH_RECOGNIZE 子句比较复杂,它的执行过程如下:

  • PARTITION BY scode 按照股票代码进行分区,可以同时分析多只股票的数据;如果省略,所有的数据作为一个整体进行分析,这一点与窗口函数类似;
  • ORDER BY tradedate 按照交易日期进行排序,用于分析股票价格随着时间变化的规律;
  • **MEASURES **定义了三个输出值,分别代表 V 型曲线的起始日期、最低点日期以及结束日期;其中的 STRT、DOWN 和 UP 都是 DEFINE 选项中定义的变量;LAST(DOWN.tradedate) 表示下降曲线中的最后一个日期,也就是最低点日期;LAST(UP.tradedate) 表示上升曲线中的最后一个日期,也就是结束日期;
  • ONE ROW PER MATCH 表示每次匹配只输出一个汇总结果;每个 V 型曲线输出一条记录;如果使用 ALL ROWS PER MATCH 选项,每个 V 型曲线都会输出构成曲线的所有节点,下文给出了相应的示例;
  • AFTER MATCH SKIP TO LAST UP 表示找到匹配的数据后,从当前 V 型曲线的最后一个上升点(UP)重新开始下一次查找;
  • **PATTERN (STRT DOWN+ UP+) **定义了需要查找的模式,使用正则表达式语法表示。从起点(STRT)开始,先下降一次或多次(DOWN+),再上升一次或多次(UP+),也就是 V 型曲线;
  • DEFINE 用于定义模式变量需要满足的条件。STRT 变量没有指定任何条件,意味着所有行都可以作为 V 型曲线的开始;DOWN 变量要求它的价格比上一行的价格更小,PREV 函数表示上一行;UP 变量要求它的价格比上一行的价格更大。

该语句返回的结果如下:

SCODE | START_DATE | BOTTOM_DATE | END_DATE |
---- - | ------------------ - | ------------------ - | ------------------ - |
S001 | 2019 - 01 - 01 00 : 00 : 00 | 2019 - 01 - 05 00 : 00 : 00 | 2019 - 01 - 06 00 : 00 : 00 |
S001 | 2019 - 01 - 06 00 : 00 : 00 | 2019 - 01 - 07 00 : 00 : 00 | 2019 - 01 - 08 00 : 00 : 00 |
S001 | 2019 - 01 - 08 00 : 00 : 00 | 2019 - 01 - 12 00 : 00 : 00 | 2019 - 01 - 13 00 : 00 : 00 |
S001 | 2019 - 01 - 18 00 : 00 : 00 | 2019 - 01 - 20 00 : 00 : 00 | 2019 - 01 - 21 00 : 00 : 00 |
S001 | 2019 - 01 - 21 00 : 00 : 00 | 2019 - 01 - 22 00 : 00 : 00 | 2019 - 01 - 27 00 : 00 : 00 |
S001 | 2019 - 01 - 27 00 : 00 : 00 | 2019 - 01 - 28 00 : 00 : 00 | 2019 - 01 - 30 00 : 00 : 00 |

查询返回了 6 条记录,分别对应了上图中的 6 个 V 型曲线。MATCH_RECOGNIZE 支持许多选项,尤其是通过 DEFINE 变量定义和 PATTERN 正则表达式模式可以实现各种复杂的趋势分析。

SQL 行模式识别(MATCH_RECOGNIZE)能够用于检测数据流中的复杂模式,具有处理复杂事件(CEP)的强大功能。常见的应用包括侦测异常的安全行为、发现金融交易行为模式、欺诈检测和传感器数据分析等。

2019 年 SQL 标准增加了第 15 部分:ISO/IEC 9075-15:2019 多维数组(SQL/MDA)。

三.SQL 与多维数组

多维数组(Multi-Dimensional Arrays)是各种科学和工程数据的核心基础结构,包括一维传感器数据、二维卫星和显微镜扫描图像、三维图像时间序列和地球物理数据、以及四维气候和海洋数据等。

大部分的编程语言,例如 C/C++、Java、Python、R 等,都提供了数组类型和相关操作的支持。早在 1999 年,SQL 就已经对数组提供了一些非常基本的支持;最新的 SQL/MDA 允许存储、访问和处理大规模的多维数组,例如 N 通道的卫星图像。这意味着 SQL 现在可以解码图像,并且通过像素坐标直接访问和处理图像区域。

image.png

其中,MDA 表示在数据库之外的数组数据,支持格式包括 TIFF、netCDF、HDF5、JSON 等;SQL/MDA 表示数据库中存储的数组数据,支持的操作包括:

  • 数组数据的摄取和存储;
  • 更新存储的数组数据;
  • 导出数组;
  • 数组和关系数据的集成查询。

以 PostgreSQL 为例,它允许将字段定义为多维数组类型,数组的元素可以是任何内置类型、自定义类型、枚举类型、复合类型等。例如:

CREATE TABLE sal_emp(
    name text,
    pay_by_quarter integer[],
    schedule text[][]
    ); INSERT INTO sal_emp
VALUES( 'Bill',
    '{10000, 10000, 10000, 10000}',
    '{{"meeting", "lunch"}, {"training", "presentation"}}' );


INSERT INTO sal_emp
VALUES( 'Carol',
    ARRAY[20000, 25000, 25000, 25000],
    ARRAY[['breakfast', 'consulting'], ['meeting', 'lunch']] );

sal_emp 表中包含两个数组字段,pay_by_quarter 是一个一维数组,schedule 是一个二维数组。

以下查询返回了 Bill 一周中的前两天计划里的第一项内容:

select schedule[1:2][1:1] from sal_emp where name = 'bill';name |-----|Carol|

使用下标可以访问数组的元素,PostgreSQL 中的数组元素从 1 开始编号。以下语句用于修改数组中的数据切片:

update sal_emp set pay_by_quarter[1:2] = '{27000,27000}'where name = 'carol';

PostgreSQL 为数组数据提供许多函数和运算符,例如以下查询使用 && 运算符查找曾经拿过 10000 报酬的员工:

select name from sal_emp where pay_by_quarter && array[10000];name|----|Bill|

unnest 函数可以将数组转换为关系表,例如:

select name, unnest(pay_by_quarter), unnest(schedule) from sal_emp;name |unnest|unnest      |-----|------|------------|Bill | 10000|meeting     |Bill | 10000|lunch       |Bill | 10000|training    |Bill | 10000|presentation|Carol| 20000|breakfast   |Carol| 25000|consulting  |Carol| 25000|meeting     |Carol| 25000|lunch       |select name, unnest( pay_by_quarter ), unnest( schedule ) from sal_emp;
name | unnest | unnest |
---- - | ------ | ------------ |
Bill | 10000 | meeting |
Bill | 10000 | lunch |
Bill | 10000 | training |
Bill | 10000 | presentation |
Carol | 20000 | breakfast |
Carol | 25000 | consulting |
Carol | 25000 | meeting |
Carol | 25000 | lunch |

PostgreSQL 还为数组提供了 GiST 和 GIN 类型的索引,可以优化数组数据的查询。

除此之外,基于 PostgreSQL 的 PostGIS Raster、Oracle GeoRaster 以及 rasdaman 数组数据库则提供了更加完善的多维数组应用场景支持。

四.SQL 与图形数据库

图形数据库(graph database)属于 NoSQL 的一种,使用节点、边和属性来表示和存储数据,使用图结构进行语义查询。图形数据库非常适合社交网络、人工智能、欺诈检测、推荐系统等领域中的复杂关系处理。Neo4j 是目前最著名的图形数据库。

image.png

2019 年 9 月 17 图形查询语言(GQL)成为了继 SQL 之后另一种新的 ISO 标准数据库查询语言。与此同时,SQL 标准将会出现一个新的第 16 部分(SQL/PGQ)(Property Graph Query),在 SQL 中直接提供一些 GQL 功能。

目前,MariaDB(OQGRAPH)、Oracle、Microsoft SQL Server 等关系型数据库都提供了图结构存储支持。上图是 Oracle 中一个金融交易系统的图形数据库示例,其中 Account、Person 和 Company 是顶点,ownerOf、worksFor 和 transaction 是边;另外,name 和 number 是顶点的属性,amount 是边的属性。它们可以使用以下数据表进行存储:

image.png

基于这些表可以创建以下属性图形:

CREATE PROPERTY GRAPH financial_transactions
VERTEX TABLES(
    Accounts LABEL Account,
    Persons LABEL Person    PROPERTIES( name ),
    Companies LABEL Company PROPERTIES( name )
    )  EDGE         TABLES(
    Transactions SOURCE KEY ( from_account ) REFERENCES Accounts
    DESTINATION KEY ( to_account ) REFERENCES Accounts
    LABEL ( transaction )PROPERTIES( amount ),
    PersonOwnerOfAccount SOURCE Persons
    DESTINATION Accounts LABEL ownerOf NO PROPERTIES,
    CompanyOwnerOfAccount SOURCE Companies
    DESTINATION Accounts LABEL ownerOf NO PROPERTIES,
    PersonWorksForCompany SOURCE Persons
    DESTINATION Companies LABEL worksFor NO PROPERTIES
    );

接下来我们就可以直接使用 SQL 语句查询图结构,例如以下语句查找所有和名叫 Nikita 的人有过交易的人员和信息:

SELECT owner.name AS account_holder, SUM( t.amount ) AS total_transacted_with_Nikita
FROM MATCH( p : Person ) -[: ownerOf] - > (account1 : Account)
, MATCH( account1 ) -[t : transaction] - (account2)    /* match both incoming and outgoing transactions */
, MATCH( account2 : Account ) < -[: ownerOf] - (owner : Person | Company)
WHERE p.name = 'Nikita'
           GROUP BY owner

其中,MATCH 子句用于遍历图形结构并返回匹配的模式,语法和 Neo4j 的 Cypher 查询语言中的 MATCH 子句非常类似。以上查询返回的结果如下:

+---------------- + ------------------------------ +
| account_holder | total_transacted_with_Nikita |
+---------------- + ------------------------------ |
| Camille | 1000.00 |
| Oracle | 4501.00 |
+---------------- + ------------------------------ +

随着 SQL 标准第 16 部分(SQL/PGQ)即将出现,我们可以在关系数据库中直接存储属性图结构数据,并且在 SQL 中进行属性图模式匹配,例如最短路径查找和最佳路径查找;SQL/PGQ 的另一个优势就是可以支持分组(GROUP BY)、聚合(AVG、SUM、COUNT 等)、排序(ORDER BY)以及许多其他的 SQL 功能。

五.SQL 与流数据处理

流数据是一组顺序、大量、快速、连续到达的数据序列,一般情况下可被视为一个随时间延续而无限增长的动态数据集合。常见的流数据包括应用程序日志文件、网购数据、游戏玩家互动数据、社交网站信息、金融交易实时数据或地理空间服务,以及来自数据中心内所连接设备或仪器的遥测数据等。

目前,常用的大数据流处理平台 Spark Streaming、Storm、Flink、ksqlDB 等都提供了 SQL 流数据处理功能;同时,一个关于流数据处理的 SQL 标准部分正在准备中。

其中,ksqlDB 是一个基于 Apache Kafka 的事件流数据库,提供了轻量级的 SQL 语句,大大降低了构建流处理应用程序所需的操作复杂性。

image.png

客户端应用程序可以采用拉取查询(pull query)和推送查询(push query)两种方式查看和订阅数据流变化。

在 ksqlDB 中创建一个数据流并运行连续查询的简单示例如下:

CREATE STREAM riderLocations( profileId VARCHAR, latitude DOUBLE, longitude DOUBLE )
WITH( kafka_topic = 'locations', value_format = 'json', partitions = 1 );


--Mountain View lat, long : 37.4133, -122.1162
SELECT * FROM riderLocations
WHERE GEO_DISTANCE( latitude, longitude, 37.4133, -122.1162 ) <= 5 EMIT CHANGES;

首先,通过一个 Kafka 主题创建一个数据流 riderLocations(骑手位置);消息内容采用 json 格式存储,例如 {“profileId”: “c2309eec”, “latitude”: 37.7877, “longitude”: -122.4205}。

然后,针对 riderLocations 数据流运行一个连续查询,返回距离 Mountain View(加州山景城)5 英里之内的骑手。该查询会一直运行,直到被终止;并且随着事件被写入 riderLocations,它会将结果推送到客户端。

此时,如果我们打开另一个会话连接到 ksqlDB,生成一些数据流:

INSERT INTO riderLocations( profileId, latitude, longitude ) VALUES( 'c2309eec', 37.7877, -122.4205 );


INSERT INTO riderLocations( profileId, latitude, longitude ) VALUES( '18f4ea86', 37.3903, -122.0643 );


INSERT INTO riderLocations( profileId, latitude, longitude ) VALUES( '4ab5cbad', 37.3952, -122.0813 );


INSERT INTO riderLocations( profileId, latitude, longitude ) VALUES( '8b6eae59', 37.3944, -122.0813 );


INSERT INTO riderLocations( profileId, latitude, longitude ) VALUES( '4a7c7b41', 37.4049, -122.0822 );


INSERT INTO riderLocations( profileId, latitude, longitude ) VALUES( '4ddad000', 37.7857, -122.4011 );

随着每次事件的生成,流查询语句将会在第一个会话中实时输出匹配的数据行。

六.总结

随着互联网和大数据等新技术的发展,SQL 早已不仅仅是当年的关系数据库查询语言了;无论是面向对象特性(例如自定义类型)、文档数据(XML、JSON)的存储和处理、时态数据的存储和处理、复杂事件和流数据处理、数据科学中的多维数组以及图形数据库等各种 NoSQL 功能已经或者即将成为 SQL 标准中的一部分,One SQL to Rule Them All!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342