本篇博客有关mysql高级特性描述均在mysql版本 5.7上使用验证。
分区表就是按照某种规则将一个大表分成若干个小表,规则可能是某一个列的值分区分,或者涉及某个列的表达式。这样大量的数据被分散到不同表中,数据量变小查询性能自然变高。
使用分区表的好处
使用分区表的限制
所有分区必须使用相同的存储引擎。
每张表的分区数有限,最大分区数目不能超过1024。
分区表达式只能是整数或者返回整数的表达式(函数)。
分区所用的字段,必须是唯一索引或者主键(主键也是唯一索引)。
无法使用外键、全文索引。
查看mysql是否支持分区
#5.6版本
SHOW VARIABLES LIKE '%partition%'
# 5.6版本之后
show plugins;
RANGE分区:基于属于一个给定连续区间的列值,把多行分配给分区。
LIST分区:类似于按RANGE分区,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择。
HASH分区:基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL 中有效的、产生非负整数值的任何表达式。
KEY分区:类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL服务器提供其自身的哈希函数。必须有一列或多列包含整数值。
创建分区表
-- 创建表
DROP TABLE IF EXISTS `sales`;
CREATE TABLE `sales` (
`id` int(11) NOT NULL,
`money` decimal(11,2) NOT NULL,
`order_date` date NOT NULL,
PRIMARY KEY (`id`,`order_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
PARTITION BY RANGE (YEAR(order_date))
(PARTITION p_2020 VALUES LESS THAN (2021) ENGINE = InnoDB,
PARTITION p_2021 VALUES LESS THAN (2022) ENGINE = InnoDB,
PARTITION p_max VALUES LESS THAN MAXVALUE ENGINE = InnoDB);
使用日期范围range 进行分区。,order_date 为分区字段 则必须包含在主键字段内,否则会报如下错误
A PRIMARY KEY must include all columns in the table's partitioning function
对于 MySQL 分区表,无法从数据库层面保证非分区列在表级别的唯一性,只能确保其在分区内的唯一性。
插入数据
-- 插入数据
INSERT INTO `sales` (`id`, `money`, `order_date`) VALUES('1','123.34','2021-12-01');
INSERT INTO `sales` (`id`, `money`, `order_date`) VALUES('2','12132.33','2022-01-15');
INSERT INTO `sales` (`id`, `money`, `order_date`) VALUES('3','32432.31','2020-03-22');
查询分区统计
-- 查询数据分区分布
SELECT
PARTITION_NAME AS "分区",TABLE_ROWS AS "行数"
FROM
information_schema.partitions
WHERE table_schema = "cds-temp"
AND table_name = "sales"
创建分区表
-- 创建表
DROP TABLE IF EXISTS `sales`;
CREATE TABLE `sales` (
`id` INT(11) NOT NULL,
`money` DECIMAL(11,2) NOT NULL,
`score` INT(2),
`order_date` DATE NOT NULL,
PRIMARY KEY (`id`,`score`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
PARTITION BY LIST(score)
(
PARTITION pEven VALUES IN (1,3,5,7,9),
PARTITION pOdd VALUES IN (2,4,6,8,10)
)
插入数据
-- 插入数据
INSERT INTO `sales` (`id`, `money`, `score`,`order_date`) VALUES('1','123.34',1,'2021-12-01');
INSERT INTO `sales` (`id`, `money`, `score`,`order_date`) VALUES('2','12132.33',2,'2022-01-15');
INSERT INTO `sales` (`id`, `money`, `score`,`order_date`) VALUES('3','32432.31',3,'2020-03-22');
INSERT INTO `sales` (`id`, `money`, `score`,`order_date`) VALUES('4','32432.31',4,'2020-03-22');
查询分区统计
-- 查询数据分区分布
SELECT
PARTITION_NAME AS "分区",TABLE_ROWS AS "行数"
FROM
information_schema.partitions
WHERE table_schema = "cds-temp"
AND table_name = "sales"
创建分区表
使用hash函数进行分区,分区数为3 分为p0、p1、p2三个分区。
DROP TABLE IF EXISTS `sales`;
CREATE TABLE `sales` (
`id` INT(11) NOT NULL,
`money` DECIMAL(11,2) NOT NULL,
`score` INT(2) NOT NULL,
`order_date` DATE NOT NULL,
PRIMARY KEY (`id`,`score`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
PARTITION BY HASH(score) PARTITIONS 3;
插入数据
-- 插入数据
INSERT INTO `sales` (`id`, `money`, `score`,`order_date`) VALUES('1','123.34',1,'2021-12-01');
INSERT INTO `sales` (`id`, `money`, `score`,`order_date`) VALUES('2','12132.33',2,'2022-01-15');
INSERT INTO `sales` (`id`, `money`, `score`,`order_date`) VALUES('3','32432.31',3,'2020-03-22');
INSERT INTO `sales` (`id`, `money`, `score`,`order_date`) VALUES('4','32432.31',4,'2020-03-22');
查询分区统计
-- 查询数据分区分布
SELECT
PARTITION_NAME AS "分区",TABLE_ROWS AS "行数"
FROM
information_schema.partitions
WHERE table_schema = "cds-temp"
AND table_name = "sales"
创建分区表
-- 创建表
DROP TABLE IF EXISTS `sales`;
CREATE TABLE `sales` (
`id` int(11) NOT NULL,
`name` varchar(10) NOT NULL,
`money` decimal(11,2) NOT NULL,
`order_date` date NOT NULL,
PRIMARY KEY (`id`,`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
PARTITION BY key (name) PARTITIONS 3;
插入数据
-- 插入数据
INSERT INTO `sales` (`id`,`name`, `money`, `order_date`) VALUES('1','汽车','123.34','2021-12-01');
INSERT INTO `sales` (`id`,`name`, `money`, `order_date`) VALUES('2','手机','12132.33','2022-01-15');
INSERT INTO `sales` (`id`,`name`, `money`, `order_date`) VALUES('3','相机','32432.31','2020-03-22');
查询分区统计
-- 查询数据分区分布
SELECT
PARTITION_NAME AS "分区",TABLE_ROWS AS "行数"
FROM
information_schema.partitions
WHERE table_schema = "cds-temp"
AND table_name = "sales"
分区表是多个底层相关表组成,mysql分区后每个分区成了独立的文件,虽然从逻辑上还是一张表其实已经分成了多张独立的表,从“information_schema.INNODB_SYS_TABLES”系统表可以看到每个分区都存在独立的TABLE_ID, 由于Innodb数据和索引都是保存在".ibd"文件当中(从INNODB_SYS_INDEXES系统表中也可以得到每个索引都是对应各自的分区(primary key和unique也不例外)),所以分区表的索引也是随着各个分区单独存储。
-- 查看创建表的分区情况
SELECT * FROM information_schema.INNODB_SYS_TABLES WHERE NAME LIKE "%sales%"
使用分区表的前提是数据量特别大,在数据量特别巨大的时候,比如10亿条数据,使用索引会产生入下问题:
大数据量的问题
分区表的两种策略
使用简单分区方式存放表,不使用其他索引,则该策略假定不依赖于内存数据,则需要控制分区表的数量限制在很小的范围内。
数据有明显的’冷热’数据之分,只有热数据被频繁访问,其他数据几乎不会被访问,热数据存放到一个分区中且该热数据尽可能的使用内存,大部分查询在该热点分区中,也可以正常使用索引。
分区表的问题
null值问题
Mysql允许分区键值为NULL,分区键可能是一个字段或者一个用户定义的表达式。一般情况下,Mysql在分区的时候会把NULL值当作零值或者一个最小值进行处理。
如果分区键所在列没有notnull约束。
range分区表,那么null行将被保存在范围最小的分区。
list分区表,那么null行将被保存到list为0的分区。
HASH和KEY分区,任何产生NULL值的表达式mysql都视同它的返回值为0。
为了避免这种情况的产生,建议分区键设置成NOT NULL。或者设置一个p_ null的“无用”分区用来存放一些null值字段。
null值问题 此处笔者存疑,因为上述分区例子中,分区字段列均为主键,说明其不存在null值字段,即时使用分区表达式也没有输出null的情况
分区列和索引列不匹配
索引列和分区列不匹配是指你的sql语句where条件的包含带有索引的列但不包含分区列的情况,这时候不管分区列有没有索引都不会进行分区过滤。
选择分区成本很高
对应范围分区和list分区,所有的curd操作都需要进行入下符合条件的行属于那些分区
,对于查找来说可能需要扫描所有分区才能判断出结果,所以分区数不宜设置的过多,小于100个分区是笔者的建议。HASH和KEY分区不会有该情况。
分区查找锁表成本
curd访问分区表时都会出现锁住所有底层表的情况,这是一个硬性开销。该情况在分区过滤之前(所以不能使用分区过滤的形式减少开销),同时也与分区类型无关。
维护分区成本
就像创建索引能提高查询效率,但也增加了索引数据维护的开销,分区也有对应的维护成本,对于频繁的删除或者修改分区会产生一定的性能损耗。
正常使用分区过滤
-- where条件只有带有分区列才能进行分区过滤
EXPLAIN PARTITIONS
SELECT
*
FROM
sales
WHERE order_date > "2022-01-01"
-- 分区表作为关联表的第二张表且关联条件是分区键,查找也是能过来分区的
EXPLAIN [ PARTITIONS ]
SELECT
*
FROM
commodity
JOIN sales commodity.date = sales.order_date
WHERE order_date > "2022-01-01"
不能使用分区过滤情况
-- mysql只有在查询条件中分区列本身进行比较 才能过滤分区
-- 如下的使用列的表达式进行筛选 则无法过滤分区,会扫描所有分区
EXPLAIN PARTITIONS
SELECT
*
FROM
sales
WHERE order_date > YEAR("2022-01-01")
合并表和分区表类似,区别在于分区表时逻辑概念,我们无法直接访问底层表,但是合并表的所有字表都是真实存在的,只是通过使用语言将两张结构一致的表合并在一起处理。
DROP TABLE IF EXISTS `user_one`;
CREATE TABLE `user_one` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `user_two`;
CREATE TABLE `user_two` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL COMMENT '姓名',
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
-- 插入数据
INSERT INTO `user_one`(`id`,`name`) VALUES (1,'张三');
INSERT INTO `user_two`(`id`,`name`) VALUES (1,'李四');
-- union 必须是多个表结构一致且两个表引擎必须为MyISAM --
-- 合并表
DROP TABLE IF EXISTS `user_mrg`;
CREATE TABLE `user_mrg` (
`id` INT(11) NOT NULL AUTO_INCREMENT ,
`name` VARCHAR(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=MERGE
UNION = (user_one,user_two) INSERT_METHOD LAST
;
INSERT INTO user_mrg() VALUE(NULL,"王五");
SELECT * FROM user_mrg
合并表user_mrg与user_one和user_two表结构定义相同,但是主键出现了重复,说明虽然合并表结构要与子表一致,但是却不受主键限制(有点匪夷所思)。
INSERT_METHOD 设置插入数据时插入到合并表中的哪个字表中,first 插入到第一张表,last 插入数据到最后一张表
MySQL 视图(View)是一种虚拟存在的表,数据库中只存放了视图的定义不存放数据,数据存放在定义视图查询涉及的真实表中。使用视图查询数据时,数据库会从真实表中取出对应的数据。
好处
简单性,简化用户对数据的理解和操作。对于经常查询的sql可以创建视图,简化复杂的sql操作。
安全性,视图可以筛选用户需要的列数据而隐藏用户不需要的列,变向的进行了简单的权限过滤。
相关语句
-- 创建视图
CREATE VIEW <视图名> [(列名 [, ...n])]
AS
SELECT语句
-- 修改视图
ALTER VIEW <视图名> [(<列名> [, ...n])]
AS
SELECT语句
-- 删除视图
DROP VIEW <视图名>
-- 创建视图
CREATE VIEW sales_view (money,order_date)
AS
SELECT money,order_date FROM sales WHERE money >500
-- 查询视图
SELECT * FROM sales_view WHERE order_date >"2020-05-01";
mysql中实现视图的方式有两种方式。以上述视图例子为例
-- 使用合并算法实现视图
SELECT
money,
order_date
FROM
sales
WHERE money > 500 -- 原视图查询条件
AND order_date > "2020-05-01" ; -- 查询创建视图 新的查询条件
-- 使用临时表实现视图
CREATE TEMPORARY TABLE sale_tmp
AS
SELECT
money,
order_date
FROM
sales
WHERE money > 500
SELECT * FROM sale_tmp WHERE order_date >"2020-05-01";
无法使用合并算法情况
group by、distinct,聚合函数、union、子查询等无法建立原表和视图表1对1映射场景 都会采用临时表形式进行视图实现。使用explain 分析视图 select_type 为DERIVED即使用临时表。
EXPLAIN SELECT * FROM {view_name}
mysql的存储代码有四种类型
好处
缺陷
存储代码功能依赖的函数较少,适于简单逻辑编写,灵活复杂的逻辑还需要依赖应用程序。
存储代码可读性,可维护性差,没有相对完善的开发和调试工具(上百行的存储代码你维护一下试试)。
存储代码没有完备的异常处理机制,一个小错误可能拖死服务。
创建触发器
DELIMITER $
CREATE TRIGGER 触发器名称
BEFORE|AFTER INSERT|UPDATE|DELETE
ON 表名
[FOR EACH ROW] -- 行级触发器
BEGIN
触发器要执行的功能;
END$
DELIMITER ;
场景:新增(删除、修改)用户(user)同时添加日志记录(user_log)
DELIMITER $
CREATE TRIGGER user_log_trigger
AFTER INSERT
ON `user`
FOR EACH ROW
BEGIN
INSERT INTO `user_log` VALUE(NULL,"add","插入用户数据");
END$
DELIMITER ;
-- 插入数据
INSERT INTO `user`(`id`,`cust_no`,`first_name`,`last_name`,`alias`,`descption`,`birthday`) VALUES (20,'T000020','周杰伦','周杰伦','zjl','1983年9月20日出生于台湾省台',"1983-01-10");
-- 查看定时策略是否开启
show variables like '%event_schedu%';
-- 开启定时策略:
set global event_scheduler=1;
CREATE
[DEFINER={user | CURRENT_USER}] -- 权限控制 只有在这里声明用户才可以使用该事件
EVENT [IF NOT EXISTS] event_name
ON SCHEDULE schedule
[ON COMPLETION [NOT] PRESERVE]
[ENABLE | DISABLE | DISABLE ON SLAVE]
[COMMENT 'comment']
DO 事件要执行的功能;
子句 | 说明 |
---|---|
DEFINER | 可选,用于定义事件执行时检查权限的用户 |
IF NOT EXISTS | 可选项,用于判断要创建的事件是否存在 |
EVENT event_name | 必选,用于指定事件名,event_name的最大长度为64个字符,如果为指定event_name,则默认为当前的MySQL用户名(不区分大小写) |
ON SCHEDULE schedule | 必选,用于定义执行的时间和时间间隔 |
ON COMPLETION [NOT] PRESERVE | 可选,用于定义事件是否循环执行,即是一次执行还是永久执行,默认为一次执行,即 NOT PRESERVE |
ENABLE | DISABLE | DISABLE ON SLAVE | 可选项,用于指定事件的一种属性。其中,关键字ENABLE表示该事件是活动的,也就是调度器检查事件是否必选调用;关键字DISABLE表示该事件是关闭的,也就是事件的声明存储到目录中,但是调度器不会检查它是否应该调用;关键字DISABLE ON SLAVE表示事件在从机中是关闭的。如果不指定这三个选择中的任意一个,则在一个事件创建之后,它立即变为活动的。 |
COMMENT ‘comment’ | 可选,用于定义事件的注释 |
DO event_body | 必选,用于指定事件启动时所要执行的代码。可以是任何有效的SQL语句、存储过程或者一个计划执行的事件。如果包含多条语句,可以使用BEGIN…END复合结构 |
在ON SCHEDULE子句中,参数schedule的值为一个AS子句,用于指定事件在某个时刻发生,其语法格式如下:
AT timestamp [+ INTERVAL interval] ...
| EVERY interval
[STARTS timestamp [+ INTERVAL interval] ...]
[ENDS timestamp [+ INTERVAL interval] ...]
参数说明:
(1)timestamp:表示一个具体的时间点,后面加上一个时间间隔,表示在这个时间间隔后事件发生。
(2)every子句:用于表示事件在指定时间区间内每隔多长时间发生一次,其中 STARTS子句用于指定开始时间;ENDS子句用于指定结束时间。
(3)interval:表示一个从现在开始的时间,其值由一个数值和单位构成。例如,使用“4 WEEK”表示4周;使用“‘1:10’ HOUR_MINUTE”表示1小时10分钟。间隔的距离用DATE_ADD()函数来支配。
interval参数值的语法格式如下:
quantity {YEAR | QUARTER | MONTH | DAY | HOUR | MINUTE |
WEEK | SECOND | YEAR_MONTH | DAY_HOUR | DAY_MINUTE |
DAY_SECOND | HOUR_MINUTE | HOUR_SECOND | MINUTE_SECOND}
创建事件
-- 每隔1秒 调用事件添加用户日志
CREATE EVENT `user_log_event`
ON SCHEDULE EVERY 1 SECOND
ON COMPLETION PRESERVE ENABLE
DO
INSERT INTO `user_log` VALUE (NULL,"time_task","定时任务触发添加用户操作日志"
);
-- 创建存储过程
CREATE PROCEDURE 存储过程名
(IN|OUT|INOUT 参数名 参数类型,...) -- IN输入参数 OUT输出参数 INOUT输入输出
[characteristics ...] -- characteristics 表示创建存储过程时指定的对存储过程的约束条件
BEGIN
存储过程体
END
参数 | 说明 |
---|---|
LANGUAGE SQL | 存储过程的实体是由SQL语句组成 |
[NOT] DETERMINISTIC | 存储过程执行结果是否确定,所谓确定:相同的输入会得到输出 |
CONTAINS SQL NO SQL READS SQL DATA MODIFIES SQL DATA |
指定子程序使用SQL语句 CONTAINS SQL:包含的SQL语句是不进行读写的SQL语句 NO SQL:不包含SQL语句 READS SQL DATA:子程序包含读请求 MODIFIES SQL DATA:子程序包含写请求 |
SQL SECURITY{ DEFINER | INVOKER} | 执行存储过程的权限。DEFINER:存储过程的创建者才有权调用,默认值 INVOKER:拥有当前存储过程访问权限的用户均有权调用。 |
COMMENT ‘string’ | 注释信息,存储过程描述 |
DELIMITER $
-- 创建储存过程 中运行
CREATE PROCEDURE sale_money_Procedure(IN s_money DECIMAL(11,2),OUT s_date DATE,INOUT s_num INT)
BEGIN
IF s_money IS NULL THEN
SELECT MAX(order_date) FROM sales INTO s_date;
ELSE
SELECT MAX(order_date) FROM sales WHERE money > s_money INTO s_date;
SET s_num = s_num * 10;
END IF;
END$
DELIMITER ;
-- 调用存储过程
SET @s_num=7;
CALL sale_money_Procedure(200.00,@max_order_date,@s_num);
SELECT @max_order_date,@s_num;
MySQL存储函数,函数一般用于计算和返回一个值,可以将经常需要使用的计算或功能写成一个函数。
CREATE FUNCTION func_name ([param_name type[,...]])
RETURNS type
[characteristic ...]
BEGIN
存储过程体
END;
-- 调用存储过程
SELECT func_name([parameter[,…]]);
-- 查看存储过程和函数的状态信息
SHOW {PROCEDURE | FUNCTION} STATUS [LIKE 'pattern']
存储函数和存储过程异同
存储函数 | 存储过程 |
---|---|
不能拥有输出参数 | 可以拥有输出参数 |
可以直接调用存储函数,不需要call语句 | 需要call语句调用存储过程 |
必须包含一条return语句 | 不允许包含return语句 |
DELIMITER $
-- 创建储存函数
CREATE FUNCTION sale_money_Function(s_money DECIMAL(11,2))
RETURNS DATE
NOT DETERMINISTIC
BEGIN
RETURN (SELECT MAX(order_date) FROM sales WHERE money > s_money);
END$
DELIMITER ;
-- 调用存储函数
SELECT sale_money_Function(200.00);