脚本宝典收集整理的这篇文章主要介绍了Mysql优化高级篇(待完善),脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。
本篇文章主要涉及mySQL的高级篇,主要是mysql的架构介绍、索引优化分析、查询截取分析、mysql锁机制以及主从复制等
在这之前的学习可参考我之前的文章进行学习
数据库知识 | 链接 |
---|---|
数据库查询常用语句语法 | 博客链接 |
数据库中增删改常用语法语句(全) | 博客链接 |
数据库中事务(Transaction)的详解 | 博客链接 |
数据库之DQL、DML、DDL、DCL详细分析 | 博客链接 |
SQLZOO(中文版)习题答案(全) | 博客链接 |
还有其他细节以及数据库的算法题 可看我的专栏 数据库细节以及数据库算法专栏
可通过安装包或者我之前的文章进行学习
linux版本可通过rpm命令或者apt-get命令或者tar进行解压等
连接服务引擎存储
架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎的架构上,插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离
。
连接层 最上层是一些客户端和连接服务,包含本地sock通信和大多数基于客户端/服务端工具实现的类似于tcplip的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
服务层 第二层架构主要完成大多少的核心服务功能,如SQL接口,并完成缓存的查询,SQL的分析和优化及部分内置函数的执行。所有跨存储引擎的功能也在这一层实现,如过程、函数等。在该层,服务器会解析查询并创建相应的内部解析树,并对其完成相应的优化如确定查询表的顺序是否利用索引等,最后生成相应的执行操作。如果是select语句,服务器还会查询内部的缓存。如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。
引擎层 存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API与存储引擎进行通信。
存储层 数据存储层,文件系统之上,完成与存储引擎的交互。
对比项 | MyISam | InnoDB |
---|---|---|
主外键 | 不支持 | 支持 |
事务 | 不支持 | 支持 |
行表锁 | 表锁,即使操作一条记录也会锁住整个表,不适合高并发的操作 | 行锁,操作时只锁某一行,不对其他行有影响,适合高并发的操作 |
缓存 | 只缓存索引,不缓存真实数据 | 不仅缓存索引还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响 |
表空间 | 小 | 大 |
关注点 | 性能 | 事务 |
默认安装 | 是 | 是 |
myIsam只关注性能,是因为他只能查询,查询后不用提交事务 而innodb关注事务,比如增删改,都要手动提交事务 不缓存真实数据,所以要查询的时候比较慢,而缓存了真实数据,查询就比较快
性能下降sql、执行时间长、等待时间长
补充F1a;索引分为单值和复合
可以通过频繁使用给他建立索引,所以查询的比较快
关联查询太多join(设计缺陷或不得已的需求)
服务器调优及各个参数设置(缓冲、线程数等)
inner join
全连接full outer join
外连接 上图中第6个的实现 可以通过如下:
由于有些mysql不能使用full join,不过可以换种方法表示 A 的独有 + AB 共有 + B的独有
union本身就可以去重 所以可以这样使用
select * From tbl_emp a left join tbl_dept b on a.deptId = b.id
union
select * from tbl_emp a right join tbl_dept b on a.deptId = b.id;
上图中第7个的实现可以通过如下: 也就是A的独有+ B的独有 之后通过union进行合并
select * from tbl_emp a left join tbl_dept b on a.deptId = b.id where b.id is null
union
select * from tbl_emp a right join tbl_dept b on a.deptId = b.id where a.deptId is null;
视频中的sql如何实践,具体这里就不给出,只需要知道其逻辑即可
具体其sql代码如下:
CREATE TABLE `tbl_emp` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` vArchar(20) DEFAULT NULL,
`deptId` int(11) DEFAULT NULL,
Primary KEY (`id`) ,
KEY `fk_dept_id`(`deptId`)
)ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8;
CREATE TABLE `tbl_dept` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`deptName` varchar(30) DEFAULT NULL,
`loCADd` varchar(40) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8;
提高效率,类比资源 排好序的、快速查找(影响order by)数据结构
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引
官方解释:
数据本身之外,数据库还维护着一个满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现高级查找算法,这种数据结构就是索引。
如下方二叉树的数据结构所示 索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录
@H_406_680@
左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址。 为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取到相应数据,从而快速的检索出符合条件的记录。
一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。
当然,除了B+树这种类型的索引之外,还有哈稀索引(hash index)等。
优势:
劣势
MysQL有大数据量的表,需要花时间研究建立最优秀的索引,或优化查询
基本语法:
CREATE [unique] INDEX indexName ON mytable(columnName(length));
//或者
ALTER mytable ADD [UNIQUE] INDEX [indexName] ON (columnName(length));
DROP INDEX [indexName] ON mytable;
SHOW INDEX FROM tableName;
添加具体有四种方式:
ALTER TABLE tbl_name ADD PRIMARY KEY (column_list);
:该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL。ALTER TABLE tbl name ADD UNIQUE index_name (column_list);
:这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)。ALTER TABLE tbl_name ADD INDEX index_name (column_list);
:添加普通索引,索引值可出现多次。ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list);
:该语句指定了索引为FULLTEXT,用于全文索引。BTree索引:
初始化介绍浅蓝色的称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示) 如磁盘块1包含数据项17和35,包含指针P1、P2、P3, P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。
真实的数据存在于叶子节点即3、5、9、10、13、15、28、29、36、60、75、79、90、99。
非叶子节点只不存储真实的数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中。
查找过程
如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO。 在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO, 29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。
真实的情况是,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。
分为有索引和无索引 索引(查找,排序)
需要建立索引的情况有:
不需要简历索引的情况有:
一个索引的选择性越接近于1,这个索引的效率就越高
MySQL Query Optimizer:Mysql中有专门负责优化SELECT语句的优化器模块,主要功能:通过计算分析系统中收集到的统计信息,为客户端请求的Query提供他认为最优的执行计划,但不见得最优
MySQL常见瓶颈(通过cpu 、io 、 服务器的硬件进行分析)
通过explain的关键字进行分析
explain的使用:explain + sql语句
关于explain的功能可以
id
:表的读取顺序。select_type
:数据读取操作的操作类型。possible_keys
:哪些索引可以使用。key
:哪些索引被实际使用。ref
:表之间的引用。rows
:每张表有多少行被优化器查询。select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序
三种情况:
id相同,执行顺序由上至下 (id都是1,执行顺序从上到下)
id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行 (在内层的子查询序列,其等级越高)
(以上两种情况同时存在的时候) 注意框框是一个临时表 按照上面的规则进行排序 id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行,衍生=DERIVED
select_type:查询的类型,主要是用于区别普通查询、联合查询、子查询等的复杂查询。
具体的类型有:
SIMPLE
- 简单的select查询,查询中不包含子查询或者UNION。PRIMARY
- 查询中若包含任何复杂的子部分,最外层查询则被标记为。(最外层)SubQUERY
- 在SELECT或WHERE列表中包含了子查询。(内层)越内层等级越高,越先执行 DERIUED
- 在FROM列表中包含的子查询被标记为DERIVED(衍生)MySQL会递归执行这些子查询,把结果放在临时表里。UNION
- 若第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在FROM子句的子查询中外层SELECT将被标记为:DERIVED。UNION RESULT
- 从UNION表获取结果的SELECT。(合并之后的查询就是这个选项)访问类型排列
type显示的是访问类型,是较为重要的一个指标,结果值从最好到最坏依次是:
System > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index >ALL
常见的指标主要有: system>const>eq_ref>ref>range>index>ALL
一般来说,得保证查询至少达到range级别,最好能达到ref。
system
:表只有一行记录(等于系统表),这是const类型的特列,平时不会出现,这个也可以忽略不计。
const
:表示通过索引一次就找到了,const用于比较primary key或者unique索引。因为只匹配一行数据,所以很快如将主键置于where列表中,MySQL就能将该查询转换为一个常量。(单表中的主键id,一张表一个条件
)
eq_ref
:唯一性索引扫描
,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫(联表唯一,和上面的区别在于索引数量不同
)
ref
:非唯一性索引扫描,返回匹配某个单独值的所有行,本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体。(上面的条件是一对一,这个条件是一对多
)
range
:只检索给定范围的行,使用一个索引来选择行。key列显示使用了哪个索引一般就是在你的where语句中出现了between、<、>、in等的查询。这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束语另一点,不用扫描全部索引。
index
:Full Index Scan,index与ALL区别为index类型只遍历索引树。这通常比ALL快,因为索引文件通常比数据文件小(也就是说虽然all和Index都是读全表,但index是从索引中读取的,而all是从硬盘中读的
)。
all
:Full Table Scan,将遍历全表以找到匹配的行。 (也就是通过 select * 全部数据读取)
possible_keys(理论上要多少索引) 显示可能应用在这张表中的索引,一个或多个。
查询涉及到的字段若存在索引,则该索引将被列出,但不一定被查询实际使用。
key(实际用到的索引) 实际使用的索引。如果为NULL,则没有使用索引 查询中若使用了覆盖索引,则该索引仅出现在key列表中
所谓的覆盖索引:查询时未发生回表
。查询的字段只能建立在索引的字段中
在不损失精确性的情况下,长度越短越好
key_len显示的值为索引字段的最大可能长度,并非实际使用长度
,即key_len是根据表定义计算而得,不是通过表内检索出的(显示使用到的条件查询,如果是常量就为const) 显示索引的哪一列被使用了,如果可能的话,是一个常数。哪些列或常量被用于查找索引列上的值。
由key_len可知t1表的idx_col1_col2被充分使用,col1匹配t2表的col1,col2匹配了一个常量,即 ‘ac’。 查询中与其它表关联的字段,外键关系建立索引。
根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数。(越小越好)
每张表被优化器查询 把不合适的索引删除,慢慢优化
第一行(执行顺序4):id列为1,表示是union里的第一个select,select_type列的primary表示该查询为外层查询,table列被标记为,表示查询结果来自一个衍生表,其中derived3中3代表该查询衍生自第三个select查询,即id为3的select。【select d1.name… 】
第二行(执行顺序2):id为3,是整个查询中第三个select的一部分。因查询包含在from中,所以为derived。【select id,namefrom t1 where other_column=’’】
第三行(执行顺序3):select列表中的子查询select_type为subquery,为整个查询中的第二个select。【select id from t3】
第四行(执行顺序1):select_type为union,说明第四个select是union里的第二个select,最先执行【select name,id from t2】
第五行(执行顺序5):代表从union的临时表中读取行的阶段,table列的<union1,4>表示用第一个和第四个select的结果进行union操作。【两个结果union操作】
建立一张单表
CREATE TABLE IF NOT EXISTS article(
id INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
author_id INT(10) UNSIGNED NOT NULL,
category_id INT(10) UNSIGNED NOT NULL,
views INT(10) UNSIGNED NOT NULL,
comments INT(10) UNSIGNED NOT NULL,
tITle VARCHAR(255) NOT NULL,
content TEXT NOT NULL
);
INSERT INTO article(author_id,category_id,views,comments,title,content)
VALUES
(1,1,1,1,'1','1'),
(2,2,2,2,'2','2'),
(1,1,3,3,'3','3');
经过如下查询:
explain SELECT id, author_id FROM article WHERE category_id = 1 AND comments > 1 ORDER BY views DESC LIMIT 1;
发现其上面的单表查询,不是索引的话,他是进行了全表查询,而且在extra还出现了Using filesort等问题
所以思路可以有建立其复合索引 具体建立复合索引有两种方式:
create index idx_article_ccv on article(category_id,comments,views);
ALTER TABLE 'article' ADD INDEX idx_article_ccv ( 'category_id , 'comments', 'views' );
发现其思路不行,所以删除其索引 DROP INDEX idx_article_ccv ON article;
这是因为按照BTree索引的工作原理,先排序category_id,如果遇到相同的category_id则再排序comments,如果遇到相同的comments 则再排序views。
当comments字段在联合索引里处于中间位置时,因comments > 1条件是一个范围值(所谓range),MySQL无法利用索引再对后面的views部分进行检索
,即range类型查询字段后面的索引无效。
所以建立复合索引是对的 但是其思路要避开中间那个范围的索引进去 只加入另外两个索引即可create index idx_article_cv on article(category_id, views);
建立两张表
CREATE TABLE IF NOT EXISTS class(
id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
card INT(10) UNSIGNED NOT NULL,
PRIMARY KEY(id)
);
CREATE TABLE IF NOT EXISTS Book(
bookid INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
card INT(10) UNSIGNED NOT NULL,
PRIMARY KEY(bookid)
);
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO class(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO book(card) VALUES(FLOOR(1+(RAND()*20)));
EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
book
ADD INDEX Y(card
);EXPLAIN SELECT * FROM class LEFT JOIN book ON class.card = book.card;
之所以只加入一个表的索引,索引就好了 那如果加入另外一个表的索引,结果还是没变化 这是因为 可以看到第二行的type变为了ref,rows也变少了,优化比较明显。这是由左连接特性决定的。LEFT JOIN条件用于确定如何从右表搜索行,左边一定都有,所以右边是我们的关键点,一定需要在右表建立索引
如果建立左索引,其实没变化
如果使用右连接RIGHT JOIN查询
换成左边是我们的关键点,要想优化需要在左表建立索引。
所以:索引两表优化,左连接右表建索引,右连接左表建索引
建立三张表: (在前面中有两张表,现在多一个表即可)
CREATE TABLE IF NOT EXISTS phone(
phoneid INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
card INT(10) UNSIGNED NOT NULL,
PRIMARY KEY(phoneid)
)ENGINE=INNODB;
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
INSERT INTO phone(card) VALUES(FLOOR(1+(RAND()*20)));
因为有用到前面的索引,所以把不必要的索引删除干净
添加必要的索引 通过后两张表 分别是alter table
phoneadd index z(
card);
,以及alter table
bookadd index y(
card);
具体详情可看我这篇文章 Mysql中各类锁的机制详细解析(全)
关于mysql的锁也可类似对比一下java的锁 详情可看我这篇文章 java中各类锁的机制详细解析(全)
二进制日志(binary LOG)
。这些记录过程叫做二进制日志事件,binary log events;中继日志(relay log) ;
。MySQL复制是异步的且串行化的
复制的原则主要有:
主从都配置在[mysqld]结点下,都是小写 具体的配置文件在window上是这么修改,主机修改my.ini
server-id=1
[必须]主服务器唯一ID
log-bin=自己本地的路径/mysqlbin
[必须]启用二进制日志
log-err=自己本地的路径/mysqlerr
[可选]启用错误日志
basedir=“自己本地路径”
[可选]根目录
tmpdir=“自己本地路径”
[可选]临时目录
datadir=“自己本地路径/Data/”
[可选]数据目录
binlog-ignore-db=mysql
[可选]设置不要复制的数据库
binlog-do-db=需要复制的主数据库名字
[可选]设置需要复制的数据库
linux上作为从机,从机修改my.cnf配置文件
关闭防火墙以及重启设备 具体可看我之前的文章进行学习补充 ubuntu:防火墙配置详细讲解(全) linux之防火墙命令firewall、iptable以及端口号等详解诠释(全)
在windows上授权 linux,只需要给予服务器ip等
GRANT REPLICATION SLAVE ON *.* TO ‘zhangsan’@‘从机器数据库IP’ IDENTIFIED BY ‘123456’;
show master status;
在linux中授权window的IP地址
CHANGE MASTER TO MASTER_HOST=’主机
IP’, MASTER_USER=‘zhangsan’, MASTER_PASSWORD=’123456’, MASTER_LOG_FILE='File名字’, MASTER_LOG_POS=Position数字;
start slave;
启动服务器的slaveshow slave statusG
stop slave;
停止以上是脚本宝典为你收集整理的Mysql优化高级篇(待完善)全部内容,希望文章能够帮你解决Mysql优化高级篇(待完善)所遇到的问题。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。