mysql实现物化视图详解及视图与物化视图区别
再一次sql优化中一个select count(*)语句因数据量实在太大,已经无法从简单的索引什么进行优化了,在同事的推荐下考虑到了物化视图
物化视图是相对于视图而言的,但是两者实际上并没有什么关系就如java/javaScript一样
首先mysql的视图不是一种物化视图,他相当于一个虚拟表,本身并不存储数据,当sql在操作视图时所有数据都是从其他表中查询出来的。者带来的问题是使用视图并不能将常用数据分离出来,优化查询速度,切操作视图的很多命令和普通标一样,这回导致在业务中无法通过sql区分表和视图,是代码变得复杂。
视图是简化设计,清晰编码的东西,他并不是提高性能的,他的存在只会降低性能(如一个视图7个表关联,另一个视图8个表,程序员不知道,觉得很方便,把两个视图关联再做一个视图,那就惨了),他的存在未了在设计上的方便性
实现视图的方法有两种,分别为合并算法和临时表算法,合并算法是指查询视图是将视图定义的sql合并到查询sql中,比如create view v1 as select * from user where sex = m;当我们要查询视图时,mysql会将select id,name from v1;并合成select id,name fromwhere sex= m; 临时表算法是将视图查出来的数据保存到一个临时表中,查询的时候查这个临时表。不管是合并算法和临时算法,都会给数据库带来额外的开销,切如果使用临时表后会使mysql的优化变得很困难,比如索引,而且视图还引入了一些其他的问题,是的其背后的逻辑非常复杂。
当然,视图在某些情况下还是可以帮助提升性能的,单视图的性能很难预测,且在mysql的优化器中,视图的代码执行路径也完全不同,无法直观预测其性能。
物化视图是是查询结果的预运算,物化视图的结果一般存储于表中。物化视图用于需要对查询立即做出响应,而又需要耗费长时间获得结果。物化视图必须能快速更新。它去介于对更新频率和内容的准确性的要求。一般来说物化视图能够在易订时间内及时更新。
Mysql本来不支持视图的。但是在5.0以上的版本,支持了视图功能,但是可惜的是不提供物化视图,但是这也难不住咱们,自己动手丰衣足食。
1.
实现自己的物化视图
看一个它是如何实现的简单的查询实例:
SELECT COUNT(*)FROM MyISAM_table;
由于计数值存储在表的头部 立即返回结果。接下来的例子会耗费几秒到数分钟。
SELECT COUNT(*) FROM innodb_huge;
对此的可能解决方案是创建一个存储所有 InnoDB 行的表。
CREATE TABLE innodb_row_count (id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, schema_name VARCHAR(64) NOT NULL, table_name VARCHAR(64) NOT NULL, row_count INT UNSIGNED NOT NULL);
取决于对该信息结果正确性的需要,该表可以每天更新一次(花费系统资源最少,结果错误最大),一小时一次甚至是极端情况下每次改变都更新(最慢)。
另一种可能就是从信息架构中读取数据。但是信息会有高达20%的错误。
SELECT table_schema, table_name, table_rows
FROM information_schema.tables WHERE table_type = ‘BASE TABLE’;
2.
更新物化视图
物化视图的更新方式有很多种。比如:
l 从不更新(只在开始更新,只用于静态数据)
l 根据需要(比如每天,比如每夜)
l 及时(每次数据修改之后)
一半使用的更新方法:
l 全部更新(速度慢,完全从无到有)
l 延时的(速度快,使用log表)
通过在日志表中存储变更信息,通常会产生简单的“快照”或者延时状况:
l 及时更新
l 完全更新
3.
测试
为了理解这个方法,我们举个例子,详细讲解一下。
CREATE TABLE sales (sales_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, product_name VARCHAR(128) NOT NULL, product_price DECIMAL(8,2) NOT NULL, product_amount SMALLINT NOT NULL);INSERT INTO sales VALUES(NULL, 'Apple', 1.25, 1), (NULL, 'Apple', 2.40, 2),(NULL, 'Apple', 4.05, 3), (NULL, 'Pear', 6.30, 2),(NULL, 'Pear', 12.20, 4), (NULL, 'Plum', 4.85, 3);SELECT * FROM sales;
我们要知道售价和每种产品获得的利润,就要使用到两次的分组查询,我们晓得在mysql中连接查询和分组排序是会用到临时表和filesort的,这个如果数据量大的话,是十分耗时,如题查询如下:
EXPLAINSELECT product_name, SUM(product_price) AS price_sum, SUM(product_amount) AS amount_sum, AVG(product_price) AS price_avg, AVG(product_amount) amount_agg, COUNT(*)FROM salesGROUP BY product_nameORDER BY price_sum /G*************************** 1. row ***************************id: 1select_type: SIMPLEtable: salestype: ALLpossible_keys: NULLkey: NULLkey_len: NULLref: NULLrows: 6Extra: Using temporary; Using filesort1 row in set (0.00 sec)
因为表中记录较少,因此速度很快,但是如果记录量很大这种,查询将会花费很多时间。
3.1
创建物化视图
DROP TABLE sales_mv;CREATE TABLE sales_mv (product_name VARCHAR(128) NOT NULL ,price_sum DECIMAL(10,2) NOT NULL,amount_sum INT NOT NULL,price_avg FLOAT NOT NULL,amount_avg FLOAT NOT NULL,sales_cnt INT NOT NULL,UNIQUE INDEX product (product_name));INSERT INTO sales_mvSELECT product_name, SUM(product_price), SUM(product_amount), AVG(product_price), AVG(product_amount), COUNT(*)FROM salesGROUP BY product_name;
最简单的方法,我们得到了预期的正确结果:
mysql> SELECT * FROM sales_mv /G*************************** 1. row ***************************product_name: Appleprice_sum: 7.70amount_sum: 6price_avg: 2.56667amount_avg: 2sales_cnt: 3*************************** 2. row ***************************product_name: Pearprice_sum: 18.50amount_sum: 6price_avg: 9.25amount_avg: 3sales_cnt: 2*************************** 3. row ***************************product_name: Plumprice_sum: 4.85amount_sum: 3price_avg: 4.85amount_avg: 3sales_cnt: 13 rows in set (0.01 sec)
这会导致我们刚才提到的“从不更新”模式失败。但是这不是我们想要的。
3.2
按需更新物化视图
根据需要更新物化视图,我们可以用存储过程来实现
DROP PROCEDURE refresh_mv_now;
DELIMITER $$
CREATE PROCEDURE refresh_mv_now (
OUT rc INT
)
BEGIN
TRUNCATE TABLE sales_mv;
INSERT INTO sales_mv
SELECT product_name
, SUM(product_price), SUM(product_amount), AVG(product_price), AVG(product_amount)
, COUNT(*)
FROM sales
GROUP BY product_name;
SET rc = 0;
END;
$$
DELIMITER ;
好!我们看下他的运行结果:
CALL refresh_mv_now(@rc);
SELECT * FROM sales_mv /G
*************************** 1. row ***************************
product_name: Apple
price_sum: 7.70
amount_sum: 6
price_avg: 2.56667
amount_avg: 2
sales_cnt: 3
*************************** 2. row ***************************
product_name: Pear
price_sum: 18.50
amount_sum: 6
price_avg: 9.25
amount_avg: 3
sales_cnt: 2
*************************** 3. row ***************************
product_name: Plum
price_sum: 4.85
amount_sum: 3
price_avg: 4.85
amount_avg: 3
sales_cnt: 1
3 rows in set (0.00 sec)
INSERT INTO sales VALUES
(NULL, ‘Apple’, 2.25, 3), (NULL, ‘Plum’, 3.35, 1)
, (NULL, ‘Pear’, 1.80, 2);
CALL refresh_mv_now(@rc);
SELECT * FROM sales_mv /G
*************************** 1. row ***************************
product_name: Apple
price_sum: 7.70
amount_sum: 6
price_avg: 2.56667
amount_avg: 2
sales_cnt: 3
*************************** 2. row ***************************
product_name: Pear
price_sum: 18.50
amount_sum: 6
price_avg: 9.25
amount_avg: 3
sales_cnt: 2
*************************** 3. row ***************************
product_name: Plum
price_sum: 4.85
amount_sum: 3
price_avg: 4.85
amount_avg: 3
sales_cnt: 1
3 rows in set (0.00 sec)
3.3
即时更新物化视图
每条语句之后做全部更新没有任何意义。但是我们想要合适的结果。做到这一点还是有点复杂的。
在每次insert sales 表我们都要更新物化视图。我们可以在sales表中透明的使用insert/update/delete触发器实现。
现在来创建需要的触发器:
DELIMITER $$CREATE TRIGGER sales_insAFTER INSERT ON salesFOR EACH ROWBEGINSET @old_price_sum = 0;SET @old_amount_sum = 0;SET @old_price_avg = 0;SET @old_amount_avg = 0;SET @old_sales_cnt = 0;SELECT IFNULL(price_sum, 0), IFNULL(amount_sum, 0), IFNULL(price_avg, 0), IFNULL(amount_avg, 0), IFNULL(sales_cnt, 0)FROM sales_mvWHERE product_name = NEW.product_nameINTO @old_price_sum, @old_amount_sum, @old_price_avg, @old_amount_avg, @old_sales_cnt;SET @new_price_sum = @old_price_sum + NEW.product_price;SET @new_amount_sum = @old_amount_sum + NEW.product_amount;SET @new_sales_cnt = @old_sales_cnt + 1;SET @new_price_avg = @new_price_sum / @new_sales_cnt;SET @new_amount_avg = @new_amount_sum / @new_sales_cnt;REPLACE INTO sales_mvVALUES(NEW.product_name, @new_price_sum, @new_amount_sum, @new_price_avg, @new_amount_avg, @new_sales_cnt);END;$$DELIMITER ;DELIMITER $$CREATE TRIGGER sales_delAFTER DELETE ON salesFOR EACH ROWBEGINSET @old_price_sum = 0;SET @old_amount_sum = 0;SET @old_price_avg = 0;SET @old_amount_avg = 0;SET @old_sales_cnt = 0;SELECT IFNULL(price_sum, 0), IFNULL(amount_sum, 0), IFNULL(price_avg, 0), IFNULL(amount_avg, 0), IFNULL(sales_cnt, 0)FROM sales_mvWHERE product_name = OLD.product_nameINTO @old_price_sum, @old_amount_sum, @old_price_avg, @old_amount_avg, @old_sales_cnt;SET @new_price_sum = @old_price_sum - OLD.product_price;SET @new_amount_sum = @old_amount_sum - OLD.product_amount;SET @new_price_avg = @new_price_sum / @new_amount_sum;SET @new_sales_cnt = @old_sales_cnt - 1;SET @new_amount_avg = @new_amount_sum / @new_sales_cnt;REPLACE INTO sales_mvVALUES(OLD.product_name, @new_price_sum, @new_amount_sum, IFNULL(@new_price_avg, 0), IFNULL(@new_amount_avg, 0), @new_sales_cnt);END;$$DELIMITER ;DELIMITER $$CREATE TRIGGER sales_updAFTER UPDATE ON salesFOR EACH ROWBEGINSET @old_price_sum = 0;SET @old_amount_sum = 0;SET @old_price_avg = 0;SET @old_amount_avg = 0;SET @old_sales_cnt = 0;SELECT IFNULL(price_sum, 0), IFNULL(amount_sum, 0), IFNULL(price_avg, 0), IFNULL(amount_avg, 0), IFNULL(sales_cnt, 0)FROM sales_mvWHERE product_name = OLD.product_nameINTO @old_price_sum, @old_amount_sum, @old_price_avg, @old_amount_avg, @old_sales_cnt;SET @new_price_sum = @old_price_sum + (NEW.product_price - OLD.product_price);SET @new_amount_sum = @old_amount_sum + (NEW.product_amount - OLD.product_amount);SET @new_sales_cnt = @old_sales_cnt;SET @new_price_avg = @new_price_sum / @new_sales_count;SET @new_amount_avg = @new_amount_sum / @new_sales_cnt;REPLACE INTO sales_mvVALUES(OLD.product_name, @new_price_sum, @new_amount_sum, IFNULL(@new_price_avg, 0), IFNULL(@new_amount_avg, 0), @new_sales_cnt);END;$$DELIMITER ;
现在来看结果:
INSERT INTO sales VALUES (NULL, 'Apple', 1.25, 1);INSERT INTO sales VALUES (NULL, 'Apple', 2.40, 2);INSERT INTO sales VALUES (NULL, 'Apple', 4.05, 3);INSERT INTO sales VALUES (NULL, 'Pear', 6.30, 2);INSERT INTO sales VALUES (NULL, 'Pear', 12.20, 4);INSERT INTO sales VALUES (NULL, 'Plum', 4.85, 3);DELETE FROM sales WHERE sales_id = 5;DELETE FROM sales WHERE sales_id = 4;UPDATE sales SET product_amount = 3 where sales_id = 2;SELECT * from sales_v;
来看一下结果:
INSERT INTO sales VALUES (NULL, 'Apple', 1.25, 1);INSERT INTO sales VALUES (NULL, 'Apple', 2.40, 2);INSERT INTO sales VALUES (NULL, 'Apple', 4.05, 3);INSERT INTO sales VALUES (NULL, 'Pear', 6.30, 2);INSERT INTO sales VALUES (NULL, 'Pear', 12.20, 4);INSERT INTO sales VALUES (NULL, 'Plum', 4.85, 3);DELETE FROM sales WHERE sales_id = 5;DELETE FROM sales WHERE sales_id = 4;UPDATE sales SET product_amount = 3 where sales_id = 2;SELECT * from sales_v;
3.4
具有快速拍摄功能的物化视图
与上面例子的不同之处在于改变并不立即使用,改变存放在日志表中在一定时间之后才更新物化视图。
除了以上的的例子我们需要另外的日志表
CREATE TABLE sales_mvl (product_name VARCHAR(128) NOT NULL, product_price DECIMAL(8,2) NOT NULL, product_amount SMALLINT NOT NULL, sales_id INT UNSIGNED NOT NULL, product_ts TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP());
不是更新mv日志是填充:
DROP TRIGGER sales_ins;DELIMITER $$CREATE TRIGGER sales_insAFTER INSERT ON salesFOR EACH ROWBEGININSERT INTO sales_mvlVALUES (NEW.product_name, NEW.product_price, NEW.product_amount, NEW.sales_id, NULL);END;$$DELIMITER ;DROP TRIGGER sales_del;DELIMITER $$CREATE TRIGGER sales_delAFTER DELETE ON salesFOR EACH ROWBEGINDELETE FROM sales_mvlWHERE sales_id = OLD.sales_id;END;$$DELIMITER ;DROP TRIGGER sales_upd;DELIMITER $$CREATE TRIGGER sales_updAFTER UPDATE ON salesFOR EACH ROWBEGINUPDATE sales_mvlSET product_name = NEW.product_name, product_price = NEW.product_price, product_amount = NEW.product_amount, sales_id = NEW.sales_id, product_ts = CURRENT_TIMESTAMP()WHERE sales_id = OLD.sales_id;END;$$DELIMITER ;
建立一个更新物化视图的存储过程。可能模式:
l 完全更新(更新至当前)
l 更新(更新到特定时间戳)
l 重建(全部重建清除MV日志表)
DELIMITER $$CREATE PROCEDURE refresh_mv (IN method VARCHAR(16), IN ts TIMESTAMP, OUT rc INT)BEGINIF UPPER(method) = 'REBUILD' THENTRUNCATE TABLE sales_mvl;TRUNCATE TABLE sales_mv;INSERT INTO sales_mvSELECT product_name, SUM(product_price), SUM(product_amount), AVG(product_price), AVG(product_amount), COUNT(*)FROM salesGROUP BY product_name;ELSEIF UPPER(method) = 'REFRESH FULL' THENREPLACE INTO sales_mvSELECT product_name, SUM(price_sum), SUM(amount_sum), SUM(price_sum)/SUM(sales_cnt), SUM(amount_sum)/SUM(sales_cnt), SUM(sales_cnt)FROM (SELECT product_name, price_sum, amount_sum, sales_cntFROM sales_mvUNION ALLSELECT product_name, SUM(product_price), SUM(product_amount), COUNT(*)FROM sales_mvlGROUP BY product_name) xGROUP BY product_name;TRUNCATE TABLE sales_mvl;SET rc = 0;ELSEIF UPPER(method) = 'REFRESH' THENREPLACE INTO sales_mvSELECT product_name, SUM(price_sum), SUM(amount_sum), SUM(price_sum)/SUM(sales_cnt), SUM(amount_sum)/SUM(sales_cnt), SUM(sales_cnt)FROM (SELECT product_name, price_sum, amount_sum, sales_cntFROM sales_mvUNION ALLSELECT product_name, SUM(product_price), SUM(product_amount), COUNT(*)FROM sales_mvlWHERE product_ts < tsGROUP BY product_name) xGROUP BY product_name;DELETEFROM sales_mvlWHERE product_ts < ts;SET rc = 0;ELSESET rc = 1;END IF;END;$$DELIMITER ;
检验是否正常工作:
INSERT INTO sales VALUES (NULL, 'Apple', 1.25, 1);wait some timeINSERT INTO sales VALUES (NULL, 'Apple', 2.40, 2);wait some timeINSERT INTO sales VALUES (NULL, 'Apple', 4.05, 3);wait some timeINSERT INTO sales VALUES (NULL, 'Pear', 6.30, 2);wait some timeINSERT INTO sales VALUES (NULL, 'Pear', 12.20, 4);wait some timeINSERT INTO sales VALUES (NULL, 'Plum', 4.85, 3);SELECT * from sales_mvl;SELECT * from sales_v;CALL refresh_mv('REFRESH', '2006-11-06 10:57:55', @rc);SELECT * from sales_v;CALL refresh_mv('REFRESH FULL', NULL, @rc);SELECT * from sales_v;CALL refresh_mv('REBUILD', NULL, @rc);SELECT * from sales_v;
4
结论
上面有说道可以通过存储过程和mysql的触发器去进行更新,不过如果是项目中的一些后台展示功能,不是很紧要的展示功能可以通过java的scheduled执行定时任务,比如,一个小时查询一次新增数据。
MySQL中的触发器(5.0.27)是不是非常快。
大量数据时,物化视图可以提高及时查询速度。
如果插入数据速度上不受限制,这个方法可以提高查询速度。
在大量数据,或者是非内存表、或者内存不够大时,可以提高性能(即,在基础表中查询耗时较大时)
注:感谢小康同学,对本文档的部分翻译
参考文献:
http://www.fromdual.com/mysql-materialized-views
然后还可以通过 Justin Swanhart的开源工具Flexviews(http://code.google.com/p/flexviews/)去实现,Flexviews比完全自己实现的解决方案更惊喜,并且提供了很多不错的功能使得可以更简单的创建和维护视图。
它由下面这些部分组成:
变更数据抓取(Change Data Capture,y CDC)功能,可以读取服务器的二进制日志并且解析相关行的变更。
一系列可以帮助创建和管理视图的定义的存储过程。
一些可以应用变更到数据库中的物化视图的工具。
对比传统的维护汇总表和缓存表的方法,Flexviews 通过提取对源表的更改,可以增量地重新计算物化视图的内容。这意味着不需要通过查询原始数据来更新视图。例如,如果创建了一张汇总表用于计算每个分组的行数,此后增加了一行数据到源表中,Flexviews简单地给相应的组的行数加一即可。同样的技术对其他的聚合函数也有效,例如SUM()和AVG()。这实际上是有好处的,基于行的二进制日志包含行更新前后的镜像,所以Flexviews 不仅仅可以获得每行的新值,还可以不需要查找源表就能知道每行数据的旧版本。计算增量数据比从源表中读取数据的效率要高得多。
因为版面的限制,这里我们不会完整地探讨怎么使用Flexviews,但是可以给出一个概略。先写出一个SELECT 语句描述想从已经存在的数据库中得到的数据。这可能包含关联和聚合(GROUP BY)。Flexviews 中有一个辅助工具可以转换SQL 语句到Flexviews 的API 调用。Flexviews 会做完所有的脏活、累活:监控数据库的变更并且转换后用于更新存储物化视图的表。现在应用可以简单地查询物化视图来替代查询需要检索的表。
Flexviews 有不错的SQL 覆盖范围,包括一些棘手的表达式,你可能没有料到一个工具可以在MySQL 服务器之外处理这些工作。这一点对创建基于复杂SQL 表达式的视图很有用,可以用基于物化视图的简单、快速的查询替换原来复杂的查询。
mysql实现物化视图详解及视图与物化视图区别相关推荐
- MySQL数据库,从入门到精通:第十四篇——MySQL视图详解
MySQL数据库,从入门到精通:第十四篇--MySQL视图详解 第 14 篇_视图 1. 常见的数据库对象 2. 视图概述 2. 1 为什么使用视图? 2. 2 视图的理解 3. 创建视图 3. 1 ...
- 十六、MySQL 视图详解
文章目录 一.常见的数据库对象 二.视图概述 2.1 为什么要使用视图? 2.2 视图的理解 2.3 视图的作用 三.视图常用操作 3.1 查看创建视图的权限 3.2 创建视图 3.3 查看视图 3. ...
- oracle查看视图定义语句_oracle视图详解
Oracle 视图详解 一. 视图的定义 视图(view),也称虚表, 不占用物理空间,这个也是相对概念,因为视图本身的定义语句还是要存储在数据字典里的.视图只有逻辑定义.每次使用的时候,只是重新执行 ...
- 详解CorelDRAW X7的多种视图显示模式
为方便用户查看编辑细节或观察整体设计效果,CorelDRAW X7软件提供多种预览模式,不同的显示模式会影响图像的显示速度和显示质量.本教程将详解CorelDRAW X7的多种视图显示模式. 点击&q ...
- Django 基于类的通用视图详解
原文出处:https://segmentfault.com/a/1190000005685454 Django 学习小组:基于类的通用视图详解(一) 通过三周的时间我们开发了一个简单的个人 Blog, ...
- NodeJS+Express+mySQL服务端开发详解
NodeJS+Express+mySQL服务端开发详解 随着NodeJS的发展,现在已经被很多人熟知,NodeJS已经成为了前端开发人员必备的技能.本文不会对NodeJS过多介绍 如果你感兴趣可以访问 ...
- mysql多表查询详解_MySQL多表查询详解上
时光在不经意间,总是过得出奇的快.小暑已过,进入中暑,太阳更加热烈的绽放着ta的光芒,...在外面被太阳照顾的人们啊,你们都是勤劳与可爱的人啊.在房子里已各种姿势看我这篇这章的你,既然点了进来,那就由 ...
- MySQL权限授权认证详解
MySQL权限授权认证详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.MySQL权限系统介绍 1>.权限系统的作用是授予来自某个主机的某个用户可以查询.插入.修改.删 ...
- Navicat的mysql远程登录方法详解
Navicat的mysql远程登录方法详解 工具和前提 远程连接方法 疑惑问题 报错汇总 关闭并删除用户 引用 工具和前提 1.均在Navicat上面进行操作: 2.Navicat15的版本,本地数据 ...
- MySQL 内部 临时表 图文 详解
MySQL 内部 临时表 图文 详解 文章目录 MySQL 内部 临时表 图文 详解 1. 准备工作 2. 哪些场景会用到临时表? 3. 临时表用哪种存储引擎? 4. 内存临时表变磁盘临时表 5. 写 ...
最新文章
- c#_未将对象引用设置到对象的实例
- JAVA springboot ssm b2b2c多用户商城系统源码:服务消费(基础)...
- 释疑のCONTEXTS
- 异常-主动抛出异常案例演练
- strak组件(9):关键字搜索
- Fedora20配置tftp服务器
- finereport 格式化金额函数_帆软报表(finereport)常用函数
- 自学Linux命令的四种方法
- Ubuntu如何启动Pycharm
- 2.5.5.2 特殊文件:/dev/null 与 /dev/tty
- 佳铁精雕机连接电脑设置_佳铁精雕机在程式里怎么更改G57之后的坐标
- myeclipse导入项目的问题,无法next
- Vitis指南 | Xilinx Vitis 系列(一)
- Selenium控制已打开的chrome、IE浏览器
- 慎用windows EFS文件加密
- 旷世科技面试题-三个均匀分布x>y>z的概率
- 购买服务器及宝塔部署环境指南
- 阿里云视频云正式支持AV1编码格式 为视频编码服务降本提效
- Connect to tfhub.dev:443 [tfhub.dev/216.58.200.238] failed 问题解决
- Centos 7 安装 ORACLE 11g
热门文章
- 【软件工程习题(含参考答案)】总复习
- VvalidationError:Invalid options object.Ignore Plugin has been initialized using an options object
- 2018蓝桥杯C/C++ A组C组题目汇总
- STM32CubeMX(2)——串口实现HC-05蓝牙模块与手机通信
- AG9311/AG9310 Type-C转HDMI设计方案|替代AG9310/AG9311芯片|GSV2201可完全替代兼容AG9310/AG9311
- 【basepro】常用util
- android计算器开发
- PB关于打印机纵向横向打印的设置
- MATLAB中的颜色控制
- A* 第k短路详解 (详尽)