-- 一个放款流水表
CREATE TABLE t_pay_flow (
id INT (11) PRIMARY KEY AUTO_INCREMENT,
uname VARCHAR (120),
pay_amt DOUBLE ,
STATUS INT (11),
insert_date DATETIME,
update_date DATETIME
);
INSERT INTO t_pay_flow VALUES(NULL, 'zs','1000','1', NOW(), NOW());
INSERT INTO t_pay_flow VALUES(NULL, 'ls','100000','0', NOW(), NOW());
INSERT INTO t_pay_flow VALUES(NULL, 'ww','2000','1', NOW(), NOW());
INSERT INTO t_pay_flow VALUES(NULL, 'zl','3000','0', NOW(), NOW());
-- 一个还款流水表
CREATE TABLE t_repay_flow (
id INT (11) PRIMARY KEY AUTO_INCREMENT,
uname VARCHAR (120),
repay_amt DOUBLE ,
STATUS INT (11),
insert_date DATETIME,
update_date DATETIME
);
INSERT INTO t_repay_flow VALUES(NULL, 'zs','1000','1', NOW(), NOW());
INSERT INTO t_repay_flow VALUES(NULL, 'ls','100000','0', NOW(), NOW());
INSERT INTO t_repay_flow VALUES(NULL, 'ww','2000','1', NOW(), NOW());
INSERT INTO t_repay_flow VALUES(NULL, 'zl','3000','0', NOW(), NOW());
视图存储了查询,当调用的时候会生成查询语句对应的结果集,一个视图可以看成是一个虚拟的表。
使用视图和使用表的方式是一样的。
视图和表同一级别,也属于数据库。
视图可以从各式各样的查询语句中创建,视图可以引用基表或者其它视图进行创建。
可以使用 连接、union、子查询等。
create [or replace] view 视图名 as select语句;
如果指定or replace,则会替换已经存在的视图。
注意: 视图和表共享数据库中相同的名称空间,视图名不能和表名相同。
注意:视图创建语句中的select语句不能将子查询作为表来使用。
练习1: 创建一个视图,表示id为1用户的放款流水信息
create view v_zs_flow as select * from t_pay_flow where id = 1;
练习2:创建一个包含用户信息以及成绩的视图
create view v_user_score as
select * from t_user as u, t_score_math as s where u.uname = s.sname;
-- 将视图改为新的select语句
alter view 视图名 as select语句;
-- 等效于以下语句, 存在则重新创建
create or replace view 视图名 as select 语句;
修改视图即替换了原有的视图。
练习1: 将视图v_zs_flow修改为id为2用户的放款流水信息
alter view v_zs_flow as select * from t_pay_flow where id = 2;
可以查看视图的定义结构:
SHOW CREATE VIEW 视图名;
练习1: 查看视图v_zs_flow的定义结构
show create view v_zs_flow;
视图可以看成一个虚拟的表,所以可以像对表操作一样操作视图。
注意: 单表的视图进行数据修改会影响原表的数据
练习1: 从视图v_zs_flow中获取所有数据信息
select * from v_zs_flow;
练习2:往视图v_zs_flow中插入姓名为lsm、金额为1000、status为1 的记录
发现基表的数据已经发生了改变,插入了一条数据:
练习3:将视图v_zs_flow的id为2的数据的金额改为500
update v_zs_flow set pay_amt = 500 where id = 2;
执行后发现基表中id为2的数据的金额也被改为500了。
练习4: 删除v_zs_flow中id为2的用户
DELETE FROM v_zs_flow WHERE id = 2;
执行后,发现基表中id为2的用户数据也被删除了。
对视图数据的操作影响了基表,如何来解决这个问题呢?
我们可以在创建视图的时候指定检查方式,从而禁止修改基表。
create view 视图名 as
select语句
-- 增加检查动作约束
with [cascade | local] check option;
默认是cascade,会对基表影响,可以指定为local选项。
可以修改视图v_zs_flow的定义:
alter view v_zs_flow as select * from t_pay_flow where id = 1
with local check option
;
此时对视图做delete操作:
DELETE FROM v_zs_flow ;
但是基表t_pay_flow的数据还在。
删除视图也是使用drop关键字,语法:
drop view 视图名;
练习1: 删除视图v_zs_flow
drop view v_zs_flow;
更多精彩请访问本文源地址: https://blog.csdn.net/zixiao217
存储过程是可以被存储在服务器中的一套SQL语句,通常和业务密切相关。一旦它被存储了,客户端不需要再重新发布单独的语句,而是可以引用存储过程来替代,类似于编译好的程序代码块。
下面一些情况下存储过程尤其有用:
存储过程和函数本质上没区别,都属于存储程序。
procedure
、函数使用 function
。
CREATE PROCEDURE 过程名 ([proc_parameter[,...]])
[characteristic ...] routine_body
proc_parameter参数列表形式:
[in | out | inout] 参数名 类型,[in | out | inout] 参数名 类型 ...
-- 其中 in、out、inout 分别表示参数是 输入参数、输出参数、既是输入参数又是输出参数
characteristic 表示存储过程特征:
characteristic:
{ CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
| SQL SECURITY { DEFINER | INVOKER }
| COMMENT 'string'
常见特性的说明:
DEFINER
、INVOKER
用于指定安全上下文,指定了谁命名谁调用该存储过程。DEFINER
指定了,则用户的形式必须是mysql用户的形式如: ‘user_name’@‘host_name’ 、CURRENT_USER 或者 CURRENT_USER() 。DEFINER
,则默认用户为执行 CREATE PROCEDURE 语句的用户,即等同于DEFINER = CURRENT_USER。存储过程执行使用 call,存储过程定义几个参数就需要传入几个。
routine_body 包含合法的SQL过程语句体。
练习1: 创建一个存储过程,用来获得t_user表的用户数
DELIMITER $$
CREATE PROCEDURE p_user_cnt (OUT param1 INT)
BEGIN
SELECT COUNT(*) INTO param1 FROM t_user;
END $$
DELIMITER ;
调用查看结果:
CALL p_user_cnt(@a)
SELECT @a
说明: delimiter命令可以用来改变查询结尾定界符;(比如改变为//、$$ 等)使得; 可被用在子程序体中。
用户自定义的变量使用 @变量名
CREATE FUNCTION 函数名 ([func_parameter[,...]]) RETURNS type
[characteristic ...] routine_body
func_parameter形式:
参数名 类型, 参数名 类型...
函数需要 RETURNS 指定返回的类型。
routine_body 包含合法的SQL过程语句体。
练习2:创建一个函数,用来返回两个参数的连接字符串
CREATE FUNCTION f_concat(param1 VARCHAR(20), param2 VARCHAR(10)) RETURNS VARCHAR(40)
RETURN CONCAT(param1, param2)
使用:
SELECT f_concat('ni', 'hao') -- nihao
注意: 无论是存储过程还是函数定义,由括号包围的参数列必须总是存在。如果没有参数,也该使用一个空参数列()。每个参数 默认都是一个IN
参数。要指定为其它参数,可在参数名之前使用关键词 OUT
或 INOUT
注意: 指定参数为IN, OUT, 或INOUT 只对PROCEDURE是合法的。(FUNCTION参数总是被认为是IN参数)
RETURNS字句只能对FUNCTION做指定,对函数而言这是强制的。它用来指定函数的返回类型,而且函数体必须包含一个RETURN value语句。
存储过程、函数可能都会包含较多的sql语句,这其中可能涉及到其他一些语法。
格式:
CALL sp_name([parameter[,...]])
可以调用存储过程。
练习: 调用 存储过程 p_user_cnt
CALL p_user_cnt(@a)
格式:
[begin_label:] BEGIN
[statement_list]
END [end_label]
可以使用BEGIN … END复合语句来包含多个语句。statement_list 代表一个或多个语句的列表。statement_list之内每个语句都必须用分号(;)来结尾。
delimiter命令可以用来改变查询结尾定界符;(比如改变为//、$$ 等)使得; 可被用在子程序体中。
DECLARE仅被用在BEGIN … END复合语句里,并且必须在复合语句的开头,在任何其它语句之前。
DECLARE局部变量
DECLARE var_name[,...] type [DEFAULT value]
这个语句被用来声明局部变量。要给变量提供一个默认值,请包含一个DEFAULT子句。值可以被指定为一个表达式,不需要为一个常数。如果没有DEFAULT子句,初始值为NULL。
局部变量的作用范围在它被声明的BEGIN … END块内。它可以被用在嵌套的块中,除了那些用相同名字 声明变量的块。
变量SET语句
SET 变量名= 值表达式
被参考变量可能是子程序内声明的变量,或者是全局变量,多个可以使用逗号分隔开。
SELECT … INTO语句给变量赋值
SELECT col_name[,...] INTO var_name[,...] from 表
表示将查询结果赋值给对应的变量。
处理程序的含义是: 通过这个语句指定每个可以处理一个或多个条件的处理程序。如果产生一个或多个条件,指定的语句被执行。 (类似于java的异常捕获处理
)
语法如下:
DECLARE handler_type HANDLER FOR condition_value[,...] sp_statement
handler_type:
CONTINUE
| EXIT
| UNDO
condition_value:
SQLSTATE [VALUE] sqlstate_value
| condition_name
| SQLWARNING
| NOT FOUND
| SQLEXCEPTION
| mysql_error_code
对一个CONTINUE类型的处理程序,当前子程序在执行 处理程序语句之后继续。对于EXIT处理程序,当前BEGIN…END复合语句的执行被终止。UNDO 处理程序类型语句还不被支持。
除了SQLSTATE值,MySQL错误代码也被支持。
示例:
CREATE TABLE t (s1 int,primary key (s1));
delimiter //
CREATE PROCEDURE handlerdemo ()
BEGIN
DECLARE CONTINUE HANDLER FOR SQLSTATE '23000' SET @x2 = 1;
SET @x = 1;
INSERT INTO t VALUES (1);
SET @x = 2;
INSERT INTO t VALUES (1);
SET @x = 3;
END;
//
CALL handlerdemo()//
SELECT @x// -- 结果为3
说明: 注意到,@x是3,这表明MySQL被执行到程序的末尾。如果DECLARE CONTINUE HANDLER FOR SQLSTATE ‘23000’ SET @x2 = 1; 这一行不在,第二个INSERT因PRIMARY KEY强制而失败之后,MySQL可能已经采取 默认(EXIT)路径,并且SELECT @x可能已经返回2。
游标可以看成是一个select语句的指针。
游标的一个常见用途就是保存查询结果,以便以后使用。
游标的结果集是由SELECT语句产生,如果处理过程需要重复使用一个记录集,那么创建一次游标而重复使用若干次,比重复查询数据库要快的多。
游标使用步骤:
DECLARE cursor_name CURSOR FOR select_statement
这个语句声明一个光标。也可以在子程序中定义多个光标,但是一个块中的每一个光标必须有唯一的名字。SELECT语句不能有INTO子句。
OPEN cursor_name
这个语句打开先前声明的光标。
FETCH cursor_name INTO var_name [, var_name] ...
这个语句用指定的打开光标读取下一行(如果有下一行的话),并且前进光标指针,并且赋值给变量。
CLOSE cursor_name
这个语句关闭先前打开的光标。
如果未被明确地关闭,光标在它被声明的复合语句的末尾被关闭。
-- 一个小额放款流水表
CREATE TABLE t_lowpay_flow (
id INT (11) PRIMARY KEY AUTO_INCREMENT,
uname VARCHAR (120),
pay_amt DOUBLE ,
STATUS INT (11),
insert_date DATETIME,
update_date DATETIME
);
/*
编写一个存储过程,从t_pay_flow表中获取,金额小于1000的全部重新写入t_lowpay_flow表
*/
delimiter $$
CREATE PROCEDURE curdemo()
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE a varchar(40);
DECLARE b double;
-- 声明游标
DECLARE cur1 CURSOR FOR SELECT uname, pay_amt FROM t_pay_flow;
-- 声明一个处理程序
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
-- 打开游标
OPEN cur1;
REPEAT
FETCH cur1 INTO a, b;
IF NOT done THEN
IF b < 1000 THEN
INSERT INTO t_lowpay_flow(uname, pay_amt) VALUES (a,b);
END IF;
END IF;
UNTIL done END REPEAT;
CLOSE cur1;
END $$
delimiter ;
-- 执行并查看小额借款标
CALL curdemo();
select * from t_lowpay_flow;
SQL也有类似java一样的流程控制语句,主要有以下几种:
if语句用于条件判断,其完整语法如下(和java里的 if..else if...else if...else
类比):
IF 条件 THEN 语句列表;
[ELSEIF 条件 THEN 语句列表]; ...
[ELSE 语句列表];
END IF;
练习: 使用if语句判断:如果还款金额大于等于借款金额,就认为结清欠款。
DELIMITER $$
CREATE PROCEDURE p_check_pay_status(OUT msg VARCHAR(40))
BEGIN
DECLARE payamt DOUBLE;
DECLARE repayamt DOUBLE;
-- declare msg varchar(40);
SELECT pay_amt, repay_amt INTO payamt, repayamt
FROM t_pay_flow AS p, t_repay_flow AS r WHERE p.uname = r.uname AND p.uname = 'zs';
IF repayamt >= payamt THEN SET msg = '结清欠款';
ELSE SET msg = '尚有欠款';
END IF;
END $$
-- 执行并获取结果
CALL p_check_pay_status(@a);
SELECT @a;
case语句和case函数(回顾一下case函数)大同小异,只是结束需要使用 end case,具体语法如下:
CASE case_value
WHEN when_value THEN statement_list
[WHEN when_value THEN statement_list] ...
[ELSE statement_list]
END CASE
-- 或者
CASE
WHEN search_condition THEN statement_list;
[WHEN search_condition THEN statement_list]; ...
[ELSE statement_list];
END CASE;
练习: 如果借款金额小于1000,则显示小额借款,否则显示普通借款
DELIMITER $$
CREATE PROCEDURE p_check_pay_status(OUT msg VARCHAR(40))
BEGIN
DECLARE payamt DOUBLE ;
SELECT pay_amt INTO payamt
FROM t_pay_flow AS p WHERE p.uname = 'zs';
CASE
WHEN payamt < 1000 THEN SET msg = '小额借款';
ELSE SET msg = '普通借款';
END CASE;
END $$
DELIMITER ;
-- 执行并获取结果
CALL p_check_pay_status(@a);
SELECT @a;
LOOP是循环语句,语法如下:
[begin_label:] LOOP
statement_list
END LOOP [end_label]
LOOP允许某特定语句或语句群的重复执行,实现一个简单的循环构造。在循环内的语句一直重复直循环被退出,退出通常伴随着一个LEAVE 语句。
LOOP语句可以被标记。如果没有指明begin_label,则end_label不能有;如果两者都出现,它们必须是同样的。
leave 语句用来退出流程控制语句体。语法如下:
LEAVE label
这个语句被用来退出任何被标注的流程控制构造。它和BEGIN … END或循环一起被使用。
练习: 结合loop和leave编写一个求1-100的累加循环的函数
DELIMITER $$
CREATE FUNCTION fun_sum( start_num INT , end_num INT)
RETURNS INT
BEGIN
DECLARE result INT DEFAULT 0; -- 注意,如果没有指定默认值,则为null
DECLARE cnt INT DEFAULT start_num;
sumlabel: LOOP
IF cnt > end_num THEN LEAVE sumlabel;
ELSE SET result = result + cnt, cnt = cnt + 1;
END IF;
END LOOP sumlabel;
RETURN result;
END $$
DELIMITER ;
-- 测试结果为 5050
SELECT fun_sum(1, 100)
ITERATE只可以出现在LOOP, REPEAT, 和WHILE语句内。ITERATE意思为:再次循环(类似于java的continue
)。语法如下:
ITERATE label
示例:
DELIMITER $$
CREATE PROCEDURE doiterate(p1 INT)
BEGIN
label1: LOOP
SET p1 = p1 + 1;
IF p1 < 10 THEN ITERATE label1; END IF;
LEAVE label1;
END LOOP label1;
SET @x = p1;
END $$
DELIMITER ;
-- 执行查看结果为 10
CALL doiterate(1)
SELECT @x
REPEAT语句内的语句或语句群被重复,直至search_condition 为真。
[begin_label:] REPEAT
statement_list
UNTIL search_condition
END REPEAT [end_label]
REPEAT 语句可以被标注。 除非begin_label也存在,end_label才能被用,如果两者都存在,它们必须是一样的。
示例:
delimiter //
CREATE PROCEDURE dorepeat(p1 INT)
BEGIN
SET @x = 0;
REPEAT
SET @x = @x + 1;
UNTIL @x > p1
END REPEAT;
END
//
delimiter ;
CALL dorepeat(1000);
SELECT @x; -- 得到1001
WHILE语句内的语句或语句群被重复,直至search_condition 为真。语法如下:
[begin_label:] WHILE search_condition DO
statement_list
END WHILE [end_label]
WHILE语句可以被标注。 除非begin_label也存在,end_label才能被用,如果两者都存在,它们必须是一样的。
示例:
delimiter $$
CREATE PROCEDURE dowhile(out result int)
BEGIN
DECLARE v1 INT DEFAULT 5;
WHILE v1 > 0 DO
SET v1 = v1 - 1;
END WHILE;
set result = v1;
END $$
delimiter ;
-- 执行存储过程
CALL dowhile(@res);
-- 获取输出参数
SELECT @res;
修改存储过程、函数 使用 alter 语句:
ALTER {PROCEDURE | FUNCTION} sp_name [characteristic ...]
说明,修改只能修改特征,并不能修改内容,如果要修改存储过程的内容,需要先删除再重建。
characteristic:
{ CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }
| SQL SECURITY { DEFINER | INVOKER }
| COMMENT 'string'
可以使用show来查看存储过程、函数定义结构
SHOW CREATE {PROCEDURE | FUNCTION} sp_name
练习: 查看存储过程p_user_cnt、函数f_concat的定义结构:
show create procedure p_user_cnt;
show create function f_concat;
删除存储过程、函数使用 drop 关键字:
drop {PROCEDURE | FUNCTION} sp_name;
练习: 删除存储过程p_user_cnt、函数f_concat
drop procedure p_user_cnt;
drop function f_concat;
触发器是数据库中与表相关的对象,当表的一个特定事件发生时,触发器就会被激活。
触发器一般定义在关联表进行 insert
、update
或者 delete
操作的时候激活。这些行操作都是触发事件。
例如,可以通过 insert
或者 load data
语句进行插入数据,此时一个insert 触发器就可以被插入的每一行给激活。
另外,还可以把触发器设置在事件之前、之后触发。
例如,你可以设置一个触发器在每一行被insert进表之前 和 每一行被update之后触发。
触发器可以用于对表中插入某个值、或者修改某个值的时候进行一些检查动作。
语法:
create trigger 触发器名 触发时机 触发事件
on 表名 for each row 触发器语句
说明:
before
、after
,标明触发器是在激活它的语句之前还是之后执行;注意事项: 对于一张表,不能有两个 before update 的触发器; 但是可以有 一个 before insert 和 一个 before update 触发器。
练习1:给t_user表增加一个触发器,当往表里插入一条数据后,也往t_score_math表插入一条用户名相同,成绩为0的数据。
DELIMITER $$
CREATE TRIGGER tri_after_insert AFTER INSERT
ON t_user FOR EACH ROW
BEGIN
INSERT INTO t_score_math VALUES(NEW.uname, 0);
END; $$
DELIMITER ;
说明: NEW、OLD 是mysql的扩展语法,在触发器里分别表示,新行、旧行。
并且对于 insert 型触发器,只能使用 NEW;对于 delete 型触发器,只能使用 OLD; 对于 update 型则可以使用 NEW、OLD。
可以使用show命令查看触发器定义结构:
SHOW CREATE TRIGGER 触发器名;
练习2:查看触发器的定义结构
show create trigger tri_after_insert;
并没有专门的类似 alter 的语法,修改触发器可以先删除再创建。
删除触发器的语法:
drop trigger 触发器名;
练习3: 删除触发器 tri_after_insert
drop trigger tri_after_insert;
存储引擎是MySQL的组件,用于处理不同类型表的SQL操作,不同的存储引擎数据存储的结构是不一样的。
InnoDB 是默认的存储引擎,也是oracle公司首推的通用存储引擎,除非你需要处理特定的案例的时候则可以选择其他存储引擎。 create table
语句默认使用InnoDB存储引擎。
MySQL使用可插拔性的存储引擎架构,以便可以在运行中的MySQL服务中装载或者卸载指定的存储引擎。
MySQL插件式存储引擎的体系结构:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o84zPy4U-1571814025255)(img\1571639095619.png)]
SHOW ENGINES
语句可以查看你当前的MySQL服务支持哪些存储引擎。
InnoDB
行级锁和oracle风格的行读取无锁提升了读写性能。InnoDB
将用户数据存储在聚集索引中,来减少基于主键的通用查询I/O开销。为了维护数据集成,InnoDB
也支持FOREIGN KEY
外键引用。表级锁
限制了读写性能,所以常用来建设只读、或者大部分情况下都是读居多的表。HEAP
引擎。但是这个引擎的使用场景已经越来越少了。你不需要受限于整个数据库都是用同一种数据库,可以针对单独的表使用不同的存储引擎。
例如,大都数情况下表都是InnoDB存储引擎的,有一个表使用的是CSV存储引擎,用于导出数据使用,一些表使用的是MEMORY存储引擎用于临时工作空间。
下表列出了各个存储引擎的相关特性,根据这些特性,可以根据自身的业务表选择合适的存储引擎。
Feature | MyISAM | Memory | InnoDB | Archive | NDB |
---|---|---|---|---|---|
B-tree indexes B树索引 | Yes | Yes | Yes | No | No |
Backup/point-in-time recovery (note 1) 及时恢复机制 | Yes | Yes | Yes | Yes | Yes |
Cluster database support | No | No | No | No | Yes |
Clustered indexes 聚合索引 | No | No | Yes | No | No |
Compressed data 数据压缩 | Yes (note 2) | No | Yes | Yes | No |
Data caches 数据缓存 | No | N/A | Yes | No | Yes |
Encrypted data | Yes (note 3) | Yes (note 3) | Yes (note 4) | Yes (note 3) | Yes (note 3) |
Foreign key support 外键支持 | No | No | Yes | No | Yes (note 5) |
Full-text search indexes 全文本索引 | Yes | No | Yes (note 6) | No | No |
Geospatial data type support | Yes | No | Yes | Yes | Yes |
Geospatial indexing support | Yes | No | Yes (note 7) | No | No |
Hash indexes 哈希索引 | No | Yes | No (note 8) | No | Yes |
Index caches 索引缓存 | Yes | N/A | Yes | No | Yes |
Locking granularity | Table | Table | Row | Row | Row |
MVCC 多版本并发控制 | No | No | Yes | No | No |
Replication support (note 1) | Yes | Limited (note 9) | Yes | Yes | Yes |
Storage limits | 256TB | RAM | 64TB | None | 384EB |
T-tree indexes | No | No | No | No | Yes |
Transactions 事务 | No | No | Yes | No | Yes |
Update statistics for data dictionary | Yes | Yes | Yes | Yes | Yes |
可以使用SHOW ENGINES
语句来查看你当前的MySQL服务支持哪些存储引擎。
还可以使用 SHOW VARIABLES LIKE '%storage_engine%';
语句来查看当前DBMS中默认的存储引擎。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o7yH50Kq-1571814025256)(img\1571639694227.png)]
在创建一张新表时,你可以通过添加ENGINE
操作在CREATE TABLE
语句中来指定表的存储引擎。
-- 设置InnoDB存储引擎
CREATE TABLE t1(i int ) ENGINE = INNODB;
CREATE TABLE t2 (i INT) ENGINE = CSV;
CREATE TABLE t3 (i INT) ENGINE = MEMORY;
如果你忽略了ENGINE操作,就会使用默认的存储引擎。MySQL目前默认使用的是InnoDB。
default-storage-engine=INNODB
来修改默认的存储引擎。
default_storage_engine
变量来指定当前会话的默认存储引擎:
set default_storage_engine = INNODB;
修改表的存储引擎,可以使用ALTER TABLE
语句:
ALTER TABLE 表名 engine = 引擎名;
InnoDB存储引擎是MySQL默认的。
InnoDB存储引擎支持 事务、B树索引、哈希索引、行级锁、外键、MVCC(多版本并发控制)等特性。
对于 InnoDB 和 MyISAM ,我们可以从以下几个方面进行比较:
对于 InnoDB 和 MyISAM,我们如何选择呢?
你可以从你的需求出发:
在数据库中进行查询操作的时候,经常需要查找特定的数据,例如: 执行 select * from t_user where id = 100000
这样的语句,mysql会从第一条记录开始遍历查找,直至找到这样的数据,显然效率是很低下的。
MySQL可以通过增加索引的方式来加快对数据表的查询操作。
索引属于表,和列是同一层次的对象结构。通过索引,我们可以提升查询操作的性能。
注意事项: 索引虽然可以提升查询效率,但是会占用存储空间,随数量的增加而增加索引存储空间,所以需要综合考虑索引的优缺点。
索引又分为以下几种:
key
或者 index
来定义,是mysql中的基本索引类型。unique
指定的索引,该索引字段必须是唯一的。fulltext
定义,只能创建类型为 char
、varchar
或者 text
的字段。旧版的MySQL全文索引只有在MyISAM实现了,新版的MySQL5.6.24上InnoDB引擎也加入了全文索引。创建索引有3种方式。
create table 表名(
列 类型 [约束],
列 类型 [约束],
[unique | fulltext ] index index_name(列, [列,...])
);
练习1: 创建一张员工表,主键id自增长、姓名、年龄、手机号,并且给手机号添加唯一索引
create table t_emp(
id int primary key auto_increment,
name varchar(40),
age int,
phone char(11),
unique index idx_phone(phone)
);
create index index_name on 表名(列);
练习2: 给员工表的姓名增加一个索引
create index idx_name on t_emp(name);
alter table 表名 add index index_name(列);
练习3:给员工表创建基于姓名、手机号的多列索引
alter table t_emp add index idx_u(name, phone);
由于索引会占用磁盘空间,所以对于不再使用的索引,应该及时删除。索引删除有2种方式:
alter table 表名 drop index 索引名;
练习1: 删除员工表的姓名索引
alter table t_emp drop index idx_name;
drop index 索引名 on 表名;
练习2:删除员工表的年龄所
我们可以使用查询缓存,可以提升查询效率。
一般而言,一条SQL语句的执行有这么几个步骤: 解析–优化–执行; MySQL中有一个配置可以开启 查询缓存,默认是关闭的,开启后,所有的查询操作将优先从缓存中进行查找,存在则返回结果。
开启查询缓存后,缓存SELECT操作的结果集和SQL语句,key为sql,value为查询结果集;
如果新的SELECT语句来了,以这个sql为key去缓存中查询,如果匹配,就把缓存的结果集返回;
匹配标准:与缓存的SQL语句是否完全一样,sql中字母区分大小写以及中间的空格,简单理解为存储了一个key-value结构,key为sql,value为sql查询结果,例如:
select age from t_user; 与 select AGE from t_user; 不会匹配,因为大小写不同;
select age from t_user; 与 select age from t_user; 不会匹配,因为空格不同;
sql两边的空格可忽略,可以认为是对key进行过trim操作之后再进行equals比较。
可以使用SHOW VARIABLES LIKE '%query_cache%';
命令查看当前查询缓存的设置选项(注意: 在MySQL 8.0.3中,这个变量被移除了。):
SHOW VARIABLES LIKE '%query_cache%';
查询缓存相关变量(了解即可):
have_query_cache
: 是否有查询缓存query_cache_limit
: MySQL能够缓存的最大查询结果;如果某查询的结果大小大于此值,则不会被缓存;query_cache_min_res_unit
: 查询缓存中分配内存的最小单位;(注意:此值通常是需要调整的,此值被调整为接近所有查询结果的平均值是最好的)
计算单个查询的平均缓存大小:(query_cache_size-Qcache_free_memory)/Qcache_queries_in_cache
query_cache_size
: 查询缓存的总体可用空间,单位为字节;其必须为1024的倍数;query_cache_type
: 查询缓存类型;是否开启缓存功能,开启方式有三种 [ON | OFF | DEMAND]
;query_cache_wlock_invalidate
: 当其它会话锁定此次查询用到的资源时,是否不能再从缓存中返回数据;(OFF表示可以从缓存中返回数据)可以使用 query_cache_type 变量来开启查询缓存,开启方式有三个值:
例如: SET QUERY_CACHE_TYPE = ON
也可以在mysql配置文件中指定开启,在 my.ini(或者my.cnf)中增加: query_cache_type = ON
查询缓存能够缓存多少数据与其缓存空间大小有关。可以通过设置 query_cache_size
的值来改变。
例如: 在 my.ini(或者my.cnf)中增加: query_cache_size = 512M
SHOW GLOBAL STATUS WHERE Variable_name='Qcache_hits' OR Variable_name='Com_select';
缓存命中率 = Qcache_hits/(Qcache_hits+Com_select)
开启了查询缓存,现在历史查询的情况如下(已经执行过 SELECT * FROM t_user;
语句):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wu7jXhpg-1571814025258)(img\1571636231425.png)]
再次执行一下SELECT * FROM t_user;
语句, 可以看到查询缓存命中的变量结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-koV4p2g1-1571814025258)(img\1571636245388.png)]
可以看到 SELECT * FROM t_user;
语句命中了查询缓存,这样提升了效率。
有以下一些场景查询缓存无法达到理想效果:
query_cache_limit
设置的值时,结果不会被缓存。
select * from mysql
。
select 1
。
练习: 多次执行 SELECT NOW();
你会发现 Qcache_hits变量的值并没有增加,即没有走缓存。
锁是计算机协调多个进程或线程并发访问某一资源的机制。
锁保证数据并发访问的一致性、有效性;
锁冲突也是影响数据库并发访问性能的一个重要因素。
锁是Mysql在服务器层和存储引擎层的的并发控制。
加锁是会消耗资源的,锁的各种操作,包括获得锁、检测锁是否是否已解除、释放锁等都会消耗资源。
锁按性能分:
MySQL 不同的存储引擎支持不同的锁机制。
锁按粒度分:
默认情况下,表锁和行锁都是自动获得的, 不需要额外的命令。
但是一些特殊场景中, 用户需要明确地进行锁表或者进行事务的控制, 以便确保整个事务的完整性,这样就需要使用事务控制和锁定语句来完成。
InnoDB 实现了以下两种类型的行锁:
为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB 还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁(意向锁是 InnoDB 自动加的, 不需用户干预):
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
。 其他 session 仍然可以查询记录,并也可以对该记录加 share mode 的共享锁。但是如果当前事务需要对该记录进行更新操作,则很有可能造成死锁。SELECT * FROM table_name WHERE ... FOR UPDATE
。其他 session 可以查询该记录,但是不能对该记录加共享锁或排他锁,而是等待获得锁。练习: 在sqlyog中打开两个连接A、B,并开启事务。在A中进行如下操作:
START TRANSACTION;
SELECT * FROM t_user FOR UPDATE;
再在B中进行如下操作:
START TRANSACTION;
INSERT INTO t_user VALUES(NULL, 'df', 50, 1);
可以看到B中语句一直处于等待状态…
MySQL表级锁有两种模式:表共享读锁(Table Read Lock)和表独占写锁(Table Write Lock)
为达到最高锁定速度,除InnoDB和BDB引擎之外,对所有存储引擎,MySQL使用表锁定(而不是页、行或者列锁定)。
对 WRITE,MySQL使用的表锁定方法原理如下:
对 READ,MySQL使用的锁定方法原理如下:
当一个锁定被释放时,锁可以被写锁队列中的线程得到,然后是读锁定队列中的线程。
这意味着,如果你在一个表上有许多更新,SELECT语句将得到没有更新才获得锁。
可以通过检查 table_locks_waited
和 table_locks_immediate
状态变量来分析系统上的表锁定争夺,如果 table_locks_waited
值比较大,就需要检查是否有很多在等待获取锁的操作了:
SHOW STATUS LIKE 'Table%';
读写锁演示可以参考:读写锁演示.md
当两个事务同时持有对方想要的资源,并且又需要等待对方释放资源,就可能产生死锁。
说明: 有两个事务A、事务B; 事务A持有资源A保持占有状态,事务B持有资源B保持占有状态; 此时事务A需要获取资源B即等待事务B释放锁,且事务B需要获取资源A即等待事务A释放锁。这样两个事务互相占有对方的资源,且不释放资源,就造成了死锁。
解决死锁问题:打破任意一个条件即可。
按以下顺序依次执行,然后查看死锁日志:
事务A执行:select * from t_user where id = 1 for update;
事务B执行:select * from t_user where id = 2 for update;
事务A执行:select * from t_user where id = 2 for update;
事务B执行:select * from t_user where id = 1 for update;
查看死锁日志:
show engine innodb status\G