MySQL(4)应用优化

MySQL应用优化

4.1-MySQL索引优化与设计

索引的作用

快速定位要查找的数据

数据库索引查找

全表扫描 VS 索引查找

如何根据首字母找到所在行

  • 二分查找
  • B+tree

InnoDB表聚簇索引

索引中只放着排序字段和ID

创建索引

  • 单列索引
create index idx_test1 on tb_student (name);
  • 联合索引
create index idx_test2 on tb_student (name, age);
  • 索引中先根据name排序,name相同的情况下,根据age排序

索引维护

  • 索引维护由数据库自动完成
  • 插入/修改/删除每一个索引行都会变成一个内部封装的事务
  • 索引越多,事务越长,代价越高
  • 索引越多对表的插入和索引字段修改就越慢
  • 控制表上索引的数量,切忌胡乱添加无用索引

如何使用索引

  • 依据WHERE查询条件建立索引
select a, b from tab_a where c=? ;
idx_c (c)
select a, b from tab_a where c=? and d=?;
idx_cd (c, d)
  • 排序order by, group by, distinct字段添加索引
select * from tb_a order by a;
select a, count(*) from tb_a group by a;
idx_a (a)

select * from tb_a order by a, b;
idx_a_b (a, b)

select * from tb_a order where c=? by a;
idx_c_a (c, a)

索引与字段选择性

  • 某个字段其值的重复程度

  • 选择性很差的字段通常不适合创建单列索引

    • 男女比例相仿的列表中性别不适合创建单列索引
    • 如果男女比例极不平衡,要查询的又是少数方(理工院校查女生)可以考虑使用索引
  • 联合索引中选择性好的字段应该排在前面

select * from tab_a where gender=? and name=?;
idx_a1 (name, gender)

联合索引与前缀查询

  • 联合索引能为前缀单列,复列查询提供帮助
idx_smp (a, b, c)
where a=? ;
where a=? and b=? ;
where a=? and c=? ;(部分ok)
  • 合理创建联合索引,避免冗余
    (a) , (a, b) , (a, b, c) X
    (a, b, c) ok

长字段上的索引

  • 在非常长的字段上建立索引影响性能
  • InnoDB索引单字段(utf8)只能取前767 bytes
  • 对长字段处理的方法
    • Email类,建立前缀索引
    Mail_addr varchar(2048)
    idx_mailadd (Mail_addr(30)) ok
    
    • 住址类,拆分字段
    Home_address varchar(2048)
    idx_mailadd (Mail_addr(30)) ? -- 很可能前半段都是相同的省市区街道名称
    Province varchar(1024), City varchar(1024), District varchar(1024), Local_address varchar(1024) ... -- 建立联合索引或单列索引
    

索引覆盖扫

  • 最核心SQL考虑索引覆盖
    select Name from tb_user where UserID=?
    Key idx_uid_name(UserID, Name)
    
  • 不需要回表获取name字段,IO最小,效率最高

无法使用索引的情况

  • 索引列进行数学运算或函数运算
    where id+1=10; X
    where id = (10-1); ok
    year(col) < 2007; X
    col < '2007-01-01'; ok
    
  • 未含符合索引的前缀字段
    Idx_abc (a, b, c):
    where b=? and c=?; X
    (b, c) ok
    
  • 前缀通配,'_'和'%'通配符
    Like '%xxx%'; X
    Like 'xxx%'; ok
    
  • where 条件使用NOT, <>, !=
  • 字段类型匹配
    • 并不绝对,但是无法预测地会造成问题,不要使用
    a int(11), idx_a (a)
    where a = '123'; X
    where a = 123 ; ok
    

利用索引排序

idx_a_b (a, b)

  • 能够使用索引帮助排序的查询:
order by a
a = 3 order by b
order by a, b
order by a desc, b desc
a > 5 order by a
  • 不能使用索引帮助排序的查询:
order by b
a > 5 order by b
a in (1, 3) order by b
order by a asc, b desc

如何确定一个查询走没走索引,走了哪个索引

  • explain是确定一个查询如何走索引最简便有效的方法
    explain select * from tb_test ;
  • 关注的项目
    • type:查询access的方式
    • key:本次查询最终选择使用哪个索引,NULL为未使用索引
    • key_len:选择的索引使用的前缀长度或者整个长度
    • rows:可以理解为查询逻辑读,需要扫描过的记录行数
    • extra:额外信息,主要指的fetch data的具体方式

4.2-MySQL数据库设计

什么是Schema设计

  • 设计数据库的表,索引,以及表和表的关系
    • 在数据模型的基础上将关系模型转化为数据库表
    • 满足业务模型需要基础上根据数据库和应用特点优化表结构

为什么Schema需要设计

  • Schema关系到应用程序功能与性能
    • 满足业务功能需要
    • 同性能密切相关
    • 数据库扩展性
    • 满足周边需求(统计,迁移等)
  • 关系型数据库修改Schema经常是高危操作
    • Schema设计要体现一定的前瞻性

完全由开发者主导的Schema设计

  • 着眼于实现当前功能
  • 完全基于功能的设计可能存在一些隐患
    • 不合理的表结构或索引设计造成性能问题
    • 没有合理评估到数据量的增长造成空间紧张而且难以维护
    • 需求频繁修改造成表结构经常变更
    • 业务重大调整导致数据经常需要重构订正

基于性能的表设计

  • 根据查询需要设计好索引
  • 根据核心查询需求,适当调整表结构
  • 基于一些特殊业务需求,调整实现方式

索引

  • 正确使用索引
  • 更新尽可能使用主键或唯一索引
  • 主键尽可能使用自增ID字段
  • 核心查询覆盖扫描
    • 用户登录需要根据用户名返回密码用于验证create index idx_uname_passwd on tb_user (username, password);
    • 建立联合索引避免回表取数据

反范式,冗余必要字段

  • 针对核心SQL保留查询结果所必须的冗余字段,避免频繁join
    • 例:消息表中冗余了每次读消息必须返回的nickname字段,避免每次读消息都变成join操作。代价是用户修改nickname成本变高。

拆分大字段

  • 拆分大字段到单独表中,避免范围扫描代价大
    • 例:博文表拆分两份,标题表只保留标题和内容缩略部分,用于快速批量返回标题列表,正文表保存大段博文内容,用于点开文章单个读取

避免过多字段或过长行

  • 根据SQL必要返回设计字段,有必要就拆表,避免过多字段
  • 一次没有必要获取那么多列数据
  • 行过长导致表数据页记录变少,范围扫描性能降低
  • 更新数据也代价增加
  • 16K也最少放2行,可能出现行迁移

分页查询

  • 避免limit + offset过大
  • 应该使用自增主键ID模拟分页
    • 第一页,直接查
    • 得到第一页的max(id)=123(一般是最后一条记录)
    • 第二页,带上id>123查询:where id>123 limit 100
    • 这样每次只需要扫描100条数据
  • 要求业务上禁止查询XX页之后的数据

热点读数据特殊处理

  • 根据数据获取的频率或数量不同对热点数据做特殊处理
    • 例1:论坛系统中置顶帖、公告贴,可以单独拆分存储,由于每次访问都要全部读出来,单独放在一起,避免每次都到普通表中随机找出来

热点写数据特殊处理

  • 根据数据获取的频率或数量不同对热点数据做特殊处理
    • 例2:微博系统中对于大量关注的热点账号消息从"推"改为"拉",避免过量insert操作。

准实时统计

  • 对不需要精确结果的计数等统计要求,建立定期更新结果表
    • 例:首页要求展示动态成交总金额,维护一个计数表,每分钟根据原表注册时间获取增量sum值更新计数表,避免每次用户刷新都要扫描交易全记录表

实时统计改进1 - 触发器实时统计

  • 对需要精确统计的计数利用数据库触发器维护计数表
    • 例:用户量冲亿活动要求实时统计,用户表上加触发器,每次有新用户插入就同时在计数表+1

实时统计改进2 - 缓存实时统计

  • 对需要精确统计的计数利用前端缓存实时维护计数
    • 例:用户量冲亿活动要求实时统计,注册数量在缓存中实时维护,每注册一个就+1,完全避免数据库读写操作。缓存万一故障失效,可从数据库整体count重新获取。

实时统计改进2 - 最大自增ID获取总数

  • 很多逻辑可以利用自增ID主键最大值直接作为总数
    • 例:用户量冲亿活动要求实时统计,用户表加上自增ID作为主键,只要取当时max(ID)就可以得到用户总数

课拓展性设计

  • 可拓展性
    • 硬件资源增长有极限的情况下处理尽可能久的线上业务
  • 数据分级,冷数据归档与淘汰
    • 可以不断释放空间供新数据使用
  • 为数据分布式做准备
    • 分库分表
    • 水平拆分
    • 牺牲一定的关系模型支持

分区表与数据淘汰

  • range分区
  • 适合数据需要定期过期的大表
  • 单个分区扫描迁移数据到历史库避免全表扫描IO开销
  • 删除单个分区非常高效

分区表与垂直分区

  • list分区
  • 适合将来可能要基于地区,类目等方式垂直拆分数据的方式
  • 清理节点上不要的数据非常高效

分区表与水平分区

  • hash分区
  • 适合将来需要做水平拆分的表
  • 清理节点上不要的数据非常高效

MySQL分区表的局限

  • 主键或唯一键必须包含在分区字段内
  • 分区字段必须是整数类型,或者加上返回整数的函数

满足周边需求

  • 为周边需求额外增加表设计
    • 为后台统计任务增加特殊索引
    • 为数据迁移或统计需求增加时间戳

统计和后台需求

  • 统计运行SQL往往和线上有很大不同
    • 利用MySQL——主多从,主从可以建不同索引的特性将统计分流到特定从库
    • 包括一些特殊用户批量查询等,所有对线上有IO压力的查询都要读写分离

自动更新时间戳

  • 统计需求经常要求从线上读走增量数据
  • 表的第一个timestamp类型字段再写入时如果不填值,会自动写入系统时间戳
  • 表的第一个timestamp类型字段每次记录发生更新后都会自动更新
  • 在update_time字段上建索引用于定时导出增量数据

Schema设计与前瞻性

  • 基于历史经验教训,预防和解决同类问题
  • 把折腾DBA够呛的所有Schema改造的原因记录并分析总结
    例:
  • 业务为例用户信息加密做了大改造
    • 数据库结果大量改动,增加了加密字段,验证策略表,所有表重新订正数据等等
    • 是否所有用到用户信息管理的应用都要去上线就用密文?
  • 程序bug误删数据,线上风险大
    • 改造业务流程,不再删除数据,加入is_deleted标记位,经常给各种表加
    • 今后的类似表是否一上线就都用标记位的方式,并加上修改原因字段?
  • 支付类应用后期做了风控改造
    • 对线上订单大表改造,加了限额,终端类型等字段
    • 遇到支付类应用,是否一上线就提示业务是否需要考虑风控并留好相关字段?

4.3-MySQL容量评估

性能容量评估

  • 分析线上业务场景
  • 评估数据库服务器所需性能指标
  • 预估可能成为瓶颈的服务器资源
  • 帮助数据库性能调优

数据库服务器硬件性能指标

  • 磁盘IO性能
  • 内存容量
  • CPU
  • 网络吞吐量
  • 磁盘容量

数据库业务特点关键词

  • OLTP/OLAP类型
  • 并发请求
  • 读写比例
  • 数据量
  • 冷热数据比
  • 数据分级存储

OLTP/OLAP

  • T = Transaction
  • 面向广大用户,高并发,较短事务操作
  • 互联网应用绝大部分属于OLTP
  • OLTP看重服务器CPU,内存,写事务较多或内存不够则依赖磁盘IO
  • A = Analytical
  • 通常面向内部人员,大规模复查询
  • OLAP看重磁盘扫描的IO能力,部分依赖内存排序

并发请求 - 衡量线上业务繁忙程度

  • 业务高峰时数据库的每秒并发访问量是多少
  • 通过应用服务器数量,连接池配置判断
  • 通过产品估算初上线用户规模和用户增长速度判断
  • 通过实际业务业务类型判断
  • 并发量相关资源:CPU

读写比例 - 描述应用程序如何使用数据库

  • 线上业务select只读与update/delete/insert写操作比例
  • delete/update通常都是先读再写
  • insert需要区分数据写入时持续insert还是大量导入数据
  • 根据业务实际场景分析
  • 多读场景相关资源:内存
  • 多写场景相关资源:磁盘IO

数据量 - 总量

  • 数据库服务器存储设备可扩容能力的上限
  • 根据估算的业务量,写入模式,分析数据增长量
  • 预估一个硬件升级周期内数据库可存放数据的总量,上线时要留好余量
  • 数据总量相关资源:磁盘容量

冷数据与热数据 - 有用数据的实时集合

  • 热数据,线上最新一定周期内将被反复访问的数据
  • 冷数据,线上保存着的,最近不会被在线用户用到的数据
  • 估算活跃用户量,数据增长量等预估热数据量
  • 内存大小尽可能足够存放线上实时热数据
  • 热数据相关资源:内存

线上数据分层存储 - 缓解线上磁盘空间压力

  • 最新热数据确保放在内存中
  • 还可能访问到的较早数据存放在线上库磁盘中
  • 更早的不会常规访问的数据定期迁移至历史库中
  • 区分哪些数据时效性强可以迁移

服务器资源选型 - 将可选方案列出来

资源指标 可选方案
磁盘IO性能 单盘 -> 盘阵; SATA -> SAS; HDD -> SSD
内存容量 较小内存 -> 较大内存
CPU 普通 -> 多核,超线程
网络吞吐量 千兆 -> 万兆; 单网卡 -> 多路;
磁盘容量 单盘 -> 盘阵; 单盘 -> LVM

案例一,网易云音乐曲库数据库服务器评估

  • 用于存放线上数千万歌曲信息
  • 确定属于OLTP线上类型数据库
  • 并发请求量
    • 50台应用服务器,每台最大连接数100
    • 可能峰值5000qps,并发请求量较大
  • CPU需求高
  • 读写比例
    • 访问模式以用户列出歌单和播放歌曲时查询歌曲信息为主,用户只有只读查询
    • 写数据发生在录入新歌或修改歌曲信息时后台操作,写比例小,且为批量导入
    • 读写比100:1
  • 数据总量
    • 估算每首歌信息8K,总计5000万,总量400G
    • 数据总量增长相对缓慢
  • 冷热数据
    • 5000万歌曲中大约40%可能被访问,10%属于热点歌曲
    • 热数据大约<=40G
  • 数据分级存储需求
    • 由于没有用户产生的数据,歌曲信息无法分级存储
  • 内存需求一般,>=40G
  • 磁盘IO能力需求一般
  • 网络流量要求,8k*2500/1024 ≈ 20MB/S,一般
资源指标 可选方案
磁盘IO性能 两块SAS做RAID1
内存容量 96G内存
CPU 2c8core超线程 相当于32核
网络吞吐量 千兆双网卡bunding
磁盘容量 900G

案例二,网易理财销售数据库服务器评估

  • 用于存放理财用户线上订单
  • 确定属于OLTP线上类型数据库
  • 业务场景有明显特征
    • 特定高息产品秒杀销售时间窗有大量并发订单写入
    • 平时只有少量订单查询和请求,和较低的常规产品购买请求
  • 评估应以满足最关键的业务高峰为基准
  • 并发请求量
    • 秒杀期间持续时间短,但是并发量预估30台应用服务器约2000tps
  • 读写比例
    • 高峰时写订单是主要开销操作
  • CPU要求高
  • 磁盘IO要求很高
  • 数据总量
    • 根据业务分析,订单属于写入瞬时量大,总量较小,单笔金额较高
    • 总量预估一年成交百万级别,增长较稳定
    • 判断数据存储需求小于200G
  • 冷热数据
    • 峰值写入为主,内存要求存放热点期间产生的脏数据即可
  • 数据分级存储需求
    • 用户订单业务约定页面展示最近半年订单,半年前的需要到历史查询页面专门查询
    • 因此可以做分级存储,迁移所有半年前的订单至历史库
  • 内存需求一般, >= 30G
  • 磁盘空间需求一般, >=200G
  • 磁盘IO能力需求很高
  • 网络要求较高
    • 并发流量较高
    • 响应速度要求高
资源指标 可选方案
磁盘IO性能 两块SSD做RAID1
内存容量 64G内存
CPU 2c8core超线程 相当于32核
网络吞吐量 万兆双网卡bunding
磁盘容量 600G

4.4-MySQL性能测试

为什么需要性能测试

  • 对线上产品缺乏心理预估
  • 重现线上异常
  • 规划未来的业务增长
  • 测试不同硬件软件配置

性能测试的分类

  • 设备层的测试
  • 业务层的测试
  • 数据库层的测试

设备层的测试

  • 关注的指标
    • 服务器、磁盘性能
    • 磁盘坏块率
    • 服务器寿命

业务层测试

  • 针对业务进行测试

数据库层测试

  • 什么情况下要做MySQL的测试
    • 测试不同的MySQL分支版本
    • 测试不同的MySQL版本
    • 测试不同的MySQL参数搭配

MySQL测试分类

  • CPU Bound
  • IO Bound

写入测试
更新测试
纯读测试
混合模式

常用的测试工具

  • 开源的MySQL性能测试工具
    • sysbench
    • tpcc-mysql
    • mysqlslap
  • 针对业务编写性能测试工具
    • blogbench

性能测试衡量指标

  • 服务吞吐量(TPS, QPS)
  • 服务响应时间
  • 服务并发性

Sysbench

  • 业界较为出名的性能测试工具
  • 可以测试磁盘、CPU、数据库
  • 支持多种数据库:Oracle, DB2, MySQL
  • 需要自己下载编译安装
  • 建议版本:sysbench0.5

编译安装Sysbench

  • 下载sysbench
    • git clone https://github.com/akopytov/sysbench.git
  • 编译&安装
    • ./autogen.sh
    • ./configure
    • make && make install

Sysbench流程

  • 常见的做法

初始化数据 -> 运行测试 -> 清理数据

Prepare语法

sysbench --test=parallel_prepare.lua --oltp_tables_count=1 --rand-init=on --oltp-table-size=500000000 --mysql-host=127.0.0.1 --mysql-port=3306 --mysql-user=sys --mysql-password=netease --mysql-db=sbtest --max-requests=0 prepare
参数 含义
--test=parallel_prepare.lua 运行导数据的脚本
--oltp_tables_count 测试需要几张表
--oltp-table-size 每张表的大小
--mysql-host MySQL Host
--mysql-port MySQL Port
--mysql-db MySQL DB
--mysql-user MySQL User
--mysql-password MySQL Password
--rand-init 是否随机初始化数据
--max-requests 执行多少个请求之后停止
prepare 执行导数据

Sysbench表结构

create table 'sbtest1'(
  'id' int(10) unsigned not null AUTO_INCREMENT,
  'k' int(10) unsigned not null DEFAULT '0',
  'c' char(120) not null DEFAULT '',
  'pad' char(60) not null DEFAULT '',
  PRIMARY KEY ('id'),
  KEY 'k_1' ('k')
) ENGINE=InnoDB AUTO_INCREMENT=3000000001 DEFAULT CHARSET=utf8 MAX_ROWS=1000000

Run语法

sysbench --test=oltp.lua --oltp_tables_count=1 --num-threads=100 --oltp-table-size=500000000 --oltp-read-only=off --report-interval=10 --rand-type=uniform --mysql-host=127.0.0.1 --mysql-port=3306 --mysql-user=sys --mysql-password=netease --mysql-db=sbtest --max-time=1000 --max-requests=0 run
参数 含义
--test=oltp.lua 需要运行的lua脚本
--oltp_tables_count 测试需要几张表
--oltp-table-size 每张表的大小
--num-threads 测试并发线程数
--oltp-read-only 是否为只读测试
--report-interval 结果输出间隔
--rand-type 数据分布模式,热点数据或者随机数据
--max-time 最大运行时间
--max-requests 执行多少个请求之后停止
prepare 开始测试

特殊情况

  • 写入测试

写入数据进行测试 -> 清理数据

cleanup

  • 手动drop掉表和database
  • 使用sysbench提供的cleanup命令
sysbench --test=parallel_prepare.lua --oltp_tables_count=1 --rand-init=on --oltp-table-size=500000000 --mysql-host=127.0.0.1 --mysql-port=3306 --mysql-user=sys --mysql-password=netease --mysql-db=sbtest --max-requests=0 cleanup

Tpcc-mysql

  • TPC-C是专门针对联机交易处理系统(OLTP系统)的规范

  • Tpcc-mysql由percona根据规范实现

  • 下载Tpcc-mysql

    • bzr branch lp:~percona-dev/perconatools/tpcc-mysql
  • 编译安装

使用Tpcc-mysql的步骤

创建表结构和索引 -> 导数据 -> 运行测试 -> 数据清理

创建表结构

  • create_table.sql
  • add_fkey_idx.sql

Tpcc-load

tpcc_load [server] [DB] [user] [pass] [warehouse]

函数 含义
server 数据库IP
DB DB名称
user 用户名
pass 密码
warehouse 仓库数量

Tpcc-start

tpcc_start -h server_host -P port -d database_name -u mysql_user -p mysql_password -w warehouse -c connections -r warmup_time -I running_time -i report-interval -f report-file
函数 含义
warehouse 仓库数量
connections 并发线程数
warmup_time 预热时间
running_time 运行时间
report_interval 输出时间间隔
report_file 输出文件

总结

  • IO Bound测试数据量要远大于内存、CPU Bound测试数据量要小于内存
  • 测试时间建议大于60分钟,减小误差
  • Sysbench更倾向于测试MySQL性能、TPCC更接近于业务
  • 运行测试程序需要同时监控机器负载,MySQL各项监控指标
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容