SQL注入、视图、触发器、事务、存储过程、函数、流程控制、索引

目录

一、SQL注入问题

SQL注入:利用特殊符号的组合产生特殊含义,从而避开正常的业务逻辑, SQL注入是比较常见的网络攻击方式之一,它不是利用操作系统的BUG来实现攻击,而是针对程序员编写时的疏忽,通过SQL语句,实现无账号登录,甚至篡改数据库。

  • python中编写登录代码
import pymysql

conn = pymysql.connect(
    host='127.0.0.1',
    port=3306,
    user='root',
    passwd='root123',
    db='db1129',
    charset='utf8',
    autocommit=True
)

cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)

# 1 获取用户名和密码
username = input('username').strip()
password = input('password').strip()

# 2 构造SQL语句
sql = "select * from register where name='%s' and pwd='%s'" % (username, password)

# 3 发送给服务端执行SQL语句
cursor.execute(sql)

# 4 获取执行结果
res = cursor.fetchall()
if res:
    print('登陆成功')
    print(res)
else:
    print('登录失败')

(1)现象一:输对用户名即可登录

  • 当我们输入用户名
# 输入
jason' --  234123dwsf 
  • 我们可以查看,待执行的sql语句
select * from register where name='jason' --  234123dwsf' and pwd=''
  • 当我们手动输入'来补全字符的拼接,然后输入--将后面密码的输入注释掉,这种注入方式,

(2)现象二:不需要对的用户名和密码也可以登陆成功

  • 当我们输入用户名
# 输入
xyz ' or 1=1 -- asdfa
  • 我们可以查看,待执行的sql语句
select * from register where name='xyz ' or 1=1 -- asdfa' and pwd=''
  • 当我们的输入包含' or 1=1 --之后,where后面条件是 name='xxxx' or 1=1则1=1是一定成立的,所以不需要知道用户名就可以直接登录

(3)解决SQL注入

针对上述的SQL注入问题,核心在于手动拼接了关键数据

所以,我们不应该手动拼接SQL语句,应该交给execute来处理,execute底层已经帮我们做好了过滤特殊符号组合产生的特殊含义的问题

# 1 获取用户名和密码
username = input('username').strip()
password = input('password').strip()

# 2 构造SQL语句 我们可以直接用%s来做格式化输入
sql = "select * from register where name=%s and pwd=%s" 

# 3 发送给服务端执行SQL语句 
# 在execute中去填写需要 %s 动态输入的数据,
cursor.execute(sql,(username,password))

(4)executemany一次性执行多条SQL语句

# executemany语法
sql = "insert into table (字段1,字段2,...字段n) values (%s,%s,...,%s)"  # 其中有%格式化输出

executemany(sql,[(值1,值2....值n),(值1,值2....值n),...])
# 使用executemany执行sql效率的更高
  • 例子
sql = "insert into register(name,pwd) values(%s,%s)"

cursor.executemany(sql, [('lili','123'),('luka','123'),('duoduo','222')])
image-20221129155855556

二、视图

1.什么是视图

视图就是通过查询得到一张虚拟表,这张表存在于内存中,当我们下次需要的时候,我们可以直接使用

这张表只有表结构,没有表数据

视图中的数据,来自于其他表

2.为什么要用视图

当没有视图的时候,一张虚拟表如果被多次调用,每次调用就需要重新创建一张虚拟表

有视图的概念后,如果要频繁使用一张虚拟表,可以将其保存下来,直接调用生成的虚拟表,来提高查询效率

视图主要用于优化查询

3.如何使用视图

  • 创建视图的语法 —关键字 view
create view 视图名 as 虚拟表;
  • 视图的名称应该见名知意,表名中包含数据来源的表

4.关于视图

(1)视图表中的数据只能查询不能对视图进行修改updateinsert因为视图表中的数据来源于原表所以不能执行增、删、改操作

在硬盘中,视图只有表结构文件,没有表数据文件

(2)视图表应该尽量少用,会和真正的表产生混淆

5.关于视图的操作

  • 查询视图
select * from 视图名;
  • 修改视图

视图修改后,修改的是原表(物理表)中的数据,虽然只建议进行查询操作,但是可以修改数据来源表

alter view 视图名 as 新的查询语句;
-- 可以修改视图的来源表
  • 删除视图
drop view 视图;

三、触发器trigger

1.触发器简介

达到某个条件之后自动触发执行

MySQL中的触发器:针对表进行增insert、删delete、改update操作能够自动触发

2.触发器触发的情况

主要有六种情况:增前、增后、删前、删后、改前、改后

create trigger 触发器的名字 before/after insert/update/delete on 表名 for each row
begin
	sql语句
end
# 当sql语句的时候触发

(1)触发器命名有一定规律

tri_after_insert_t1
tri_before_delete_t1
tri_after_update_t1
# 命名规律
tri触发器_after/begin_操作insert/update/delete_表名

(2)临时修改SQL语句的结束符

由于有些操作中需要使用分号,这个时候就需要用关键字delimiter修改结束符分号

# 语法
delimiter 临时声明结束符

# 如
delimiter $$
-- 使用完成之后改回来
delimiter ;

(3)触发器实际应用

CREATE TABLE cmd (
    id INT PRIMARY KEY auto_increment,
    USER CHAR (32),
    priv CHAR (10),
    cmd CHAR (64),
    sub_time datetime, #提交时间
    success enum ('yes', 'no') #0代表执行失败
);

CREATE TABLE errlog (
    id INT PRIMARY KEY auto_increment,
    err_cmd CHAR (64),
    err_time datetime
);

delimiter $$  # 将mysql默认的结束符由;换成$$
create trigger tri_after_insert_cmd after insert on cmd for each row
begin
    if NEW.success = 'no' then  # 新记录都会被MySQL封装成NEW对象
        insert into errlog(err_cmd,err_time) values(NEW.cmd,NEW.sub_time);
    end if;
end $$
delimiter ;  # 结束之后记得再改回来,不然后面结束符就都是$$了

#往表cmd中插入记录,触发触发器,根据IF的条件决定是否插入错误日志
INSERT INTO cmd (
    USER,
    priv,
    cmd,
    sub_time,
    success
)
VALUES
    ('kevin','0755','ls -l /etc',NOW(),'yes'),
    ('kevin','0755','cat /etc/passwd',NOW(),'no'),
    ('kevin','0755','useradd xxx',NOW(),'no'),
    ('kevin','0755','ps aux',NOW(),'yes');

# 查询errlog表记录
select * from errlog;
# 查看所有的触发器
show triggers;
# 删除触发器
drop trigger tri_after_insert_cmd;

image-20221129102239531

错误日志

4.触发器的增删改查

show triggers;

# 针对插入
create trigger tri_after_insert_t1 after insert on 表名 for each row
begin
    sql代码。。。
end 
create trigger tri_after_insert_t2 before insert on 表名 for each row
begin
    sql代码。。。
end

# 针对删除
create trigger tri_after_delete_t1 after delete on 表名 for each row
begin
    sql代码。。。
end
create trigger tri_after_delete_t2 before delete on 表名 for each row
begin
    sql代码。。。
end

# 针对修改
create trigger tri_after_update_t1 after update on 表名 for each row
begin
    sql代码。。。
end
create trigger tri_after_update_t2 before update on 表名 for each row
begin
    sql代码。。。
end

四、事务Transaction

在关系数据库中,一个事务可以是一条SQL语句,一组SQL语句或整个程序。

1.事务的四大特性 ACID

A:atomicity原子性一个事务是一个不可分割的工作单位,要么同时成功,要么同时失败

C:consistency一致性,事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。

I:isolation隔离性,一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

D:durability持久性,持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

2.数据库操作

mysql是完整事务

执行rollback或者commit标志着事务结束

create table user(
id int primary key auto_increment,
name char(32),
balance int
);

insert into user(name,balance)
values
('jason',1000),
('kevin',1000),
('tank',1000);

# 修改数据之前先开启事务操作
start transaction;

# 修改操作
update user set balance=900 where name='jason'; #买支付100元
update user set balance=1010 where name='kevin'; #中介拿走10元
update user set balance=1090 where name='tank'; #卖家拿到90元

# 回滚到上一个状态
rollback;

# 开启事务之后,只要没有执行commit操作,数据其实都没有真正刷新到硬盘
commit;

3.事务相关关键字

start transaction

rollback

commit

savepoint 节点,(部分事务)

4.隔离级别

在SQL标准中定义了四种隔离级别,每一种级别都规定了一个事务中所做的修改
InnoDB支持所有隔离级别
	set transaction isolation level 级别
1.read uncommitted(未提交读)
	事务中的修改即使没有提交,对其他事务也都是可见的,事务可以读取未提交的数据,这一现象也称之为"脏读"
2.read committed(提交读)
	大多数数据库系统默认的隔离级别
  一个事务从开始直到提交之前所作的任何修改对其他事务都是不可见的,这种级别也叫做"不可重复读"
3.repeatable read(可重复读)		# MySQL默认隔离级别
	能够解决"脏读"问题,但是无法解决"幻读"
  所谓幻读指的是当某个事务在读取某个范围内的记录时另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录会产生幻行,InnoDB和XtraDB通过多版本并发控制(MVCC)及间隙锁策略解决该问题
4.serializable(可串行读)
	强制事务串行执行,很少使用该级别
  • MVCC
MVCC只能在read committed(提交读)、repeatable read(可重复读)两种隔离级别下工作,其他两个不兼容(read uncommitted:总是读取最新  serializable:所有的行都加锁)

InnoDB的MVCC通过在每行记录后面保存两个隐藏的列来实现MVCC
	一个列保存了行的创建时间
  一个列保存了行的过期时间(或删除时间)  # 本质是系统版本号
每开始一个新的事务版本号都会自动递增,事务开始时刻的系统版本号会作为事务的版本号用来和查询到的每行记录版本号进行比较

例如
刚插入第一条数据的时候,我们默认事务id为1,实际是这样存储的
    username		create_version		delete_version
    jason						1					
可以看到,我们在content列插入了kobe这条数据,在create_version这列存储了1,1是这次插入操作的事务id。
然后我们将jason修改为jason01,实际存储是这样的
    username		create_version		delete_version
    jason						1									2
    jason01					2
可以看到,update的时候,会先将之前的数据delete_version标记为当前新的事务id,也就是2,然后将新数据写入,将新数据的create_version标记为新的事务id
当我们删除数据的时候,实际存储是这样的
		username		create_version		delete_version
    jason01					2									 3
"""
由此当我们查询一条记录的时候,只有满足以下两个条件的记录才会被显示出来:
   1.当前事务id要大于或者等于当前行的create_version值,这表示在事务开始前这行数据已经存在了。
   2.当前事务id要小于delete_version值,这表示在事务开始之后这行记录才被删除。
"""

五、存储过程procedure

存储过程包含了一系列可以执行的SQL语句,存储过程存放于MySQL中,通过调用它的名字可以执行其内部的SQL语句,可以看成是python中的自定义函数

1.无参函数

delimiter $$
create procedure p1()
begin
	select * from cmd;
end $$
delimiter ;

# 调用
call p1()

2.有参函数

delimiter $$
create procedure p2(
    in m int,  # in表示这个参数必须只能是传入不能被返回出去
    in n int,  
    out res int  # out表示这个参数可以被返回出去,还有一个inout表示即可以传入也可以被返回出去
)
begin
    select * from cmd where id > m and id < n;
    set res=0;  # 用来标志存储过程是否执行
end $$
delimiter ;

# 针对res需要先提前定义
set @res=10;  定义
select @res;  查看 -- res=10
call p2(1,5,@res)  调用  -- 展示id为2,3,4的数据,并重制@res
select @res  查看 -- @res=0

3.关于存储过程的操作

  • 查看存储过程具体信息
show create procedure pro1;
  • 查看所有存储过程
show procedure status;
  • 删除存储过程
drop procedure pro1;

4.如何使用存储过程

前提:存储过程在哪个库下面创建的只能在对应的库下面才能使用

# 1、直接在mysql中调用
set @res=10  # res的值是用来判断存储过程是否被执行成功的依据,所以需要先定义一个变量@res存储10
call p1(2,4,10);  # 报错
call p1(2,4,@res);  

# 查看结果
select @res;  # 执行成功,@res变量值发生了变化

# 2、在python程序中调用
pymysql链接mysql
产生的游表cursor.callproc('p1',(2,4,10))  # 内部原理:@_p1_0=2,@_p1_1=4,@_p1_2=10;
cursor.excute('select @_p1_2;')


# 3、存储过程与事务使用举例(了解)
delimiter //
create PROCEDURE p5(
    OUT p_return_code tinyint
)
BEGIN
    DECLARE exit handler for sqlexception
    BEGIN
        -- ERROR
        set p_return_code = 1;
        rollback;
    END;


  DECLARE exit handler for sqlwarning
  BEGIN
      -- WARNING
      set p_return_code = 2;
      rollback;
  END;

  START TRANSACTION;
      update user set balance=900 where id =1;
      update user123 set balance=1010 where id = 2;
      update user set balance=1090 where id =3;
  COMMIT;

  -- SUCCESS
  set p_return_code = 0; #0代表执行成功


END //
delimiter ;

六、MySQL函数

注意与存储过程的区别,mysql内置的函数只能在sql语句中使用,可以看成python中的内置函数

函数名 作用
Trim ltrim rtrim 移除指定字符
lower`` upper 大小写转换
Left right 获取左右起始指定个数字符
soundex
date_format 日期格式

1.移除指定字符Trim ltrim rtrim

  • Trim 移除指定字符
# 默认去除字符前后的空格
SELECT TRIM('  bar   ');
        -> 'bar'

# leading关键字,默认移除字符串左侧的指定字符
SELECT TRIM(LEADING 'x' FROM 'xxxbarxxx');
        -> 'barxxx'

# both关键字,默认移除左右的指定的字符
SELECT TRIM(BOTH 'x' FROM 'xxxbarxxx');
        -> 'bar'

# trailing关键字,默认移除尾部指定的字符
SELECT TRIM(TRAILING 'xyz' FROM 'barxxyz');
        -> 'barx'
  • ltrim 指定移除字符串左边的空格
SELECT LTRIM('  barbar');
        -> 'barbar'
  • rtrim指定移除字符串右边的空格
SELECT RTRIM('barbar   ');
        -> 'barbar'

2.upperlower转换大小写

  • lower转换成小写
# 转换成小写
SELECT LOWER('QUADRATICALLY');
        -> 'quadratically'

/*当应用于二进制字符串时,LOWER()(和UPPER())无效 
(二进制,varbinary, blob)。若要执行字母转换,请执行convert 
将字符串转换为非二进制字符串:*/
SET @str = BINARY 'New York';
SELECT LOWER(@str), LOWER(CONVERT(@str USING latin1));

# 结果
+-------------+-----------------------------------+
| LOWER(@str) | LOWER(CONVERT(@str USING latin1)) |
+-------------+-----------------------------------+
| New York    | new york                          |
+-------------+-----------------------------------+
  • upper转换成大写
SELECT UPPER('Hej');
        -> 'HEJ'

3.left right获取左右起始指定个数的字符

  • left right
# left
SELECT LEFT('foobarbar', 5);
        -> 'fooba'
      
# right      
SELECT RIGHT('foobarbar', 4);
        -> 'rbar'        

4.Soundex返回读音相似值(对英文效果)

客户表中有一个顾客登记的用户名为J.Lee,但如果这是输入错误真名其实叫J.Lie,可以使用soundex匹配发音类似的

where Soundex(name)=Soundex('J.Lie')

5.date_format日期格式

CREATE TABLE blog (
    id INT PRIMARY KEY auto_increment,
    NAME CHAR (32),
    sub_time datetime
);
INSERT INTO blog (NAME, sub_time)
VALUES
    ('第1篇','2015-03-01 11:31:21'),
    ('第2篇','2015-03-11 16:31:21'),
    ('第3篇','2016-07-01 10:21:31'),
    ('第4篇','2016-07-22 09:23:21'),
    ('第5篇','2016-07-23 10:11:11'),
    ('第6篇','2016-07-25 11:21:31'),
    ('第7篇','2017-03-01 15:33:21'),
    ('第8篇','2017-03-01 17:32:21'),
    ('第9篇','2017-03-01 18:31:21');
select date_format(sub_time,'%Y-%m'),count(id) from blog group by date_format(sub_time,'%Y-%m');

1.where Date(sub_time) = '2015-03-01'
2.where Year(sub_time)=2016 AND Month(sub_time)=07;
# 更多日期处理相关函数 
	adddate	增加一个日期 
	addtime	增加一个时间
	datediff	计算两个日期差值
  ...

七、流程控制

  • if条件语句
delimiter //
CREATE PROCEDURE proc_if ()
BEGIN
    
    declare i int default 0;
    if i = 1 THEN
        SELECT 1;
    ELSEIF i = 2 THEN
        SELECT 2;
    ELSE
        SELECT 7;
    END IF;

END //
delimiter ;
  • while循环
delimiter //
CREATE PROCEDURE proc_while ()
BEGIN

    DECLARE num INT ;
    SET num = 0 ;
    WHILE num < 10 DO
        SELECT
            num ;
        SET num = num + 1 ;
    END WHILE ;

END //
delimiter ;

八、索引

1.索引的作用

1)索引就好比一本书的目录,它能让你更快的找到自己想要的内容。
2)让获取的数据更有目的性,从而提高数据库检索数据的性能。

2.索引类型介绍

1)BTREE:B+树索引
2)HASH:HASH索引
3)FULLTEXT:全文索引
4)RTREE:R树索引

3.索引管理

索引建立在表的列上(字段)的。

在 where 后面的列建立索引才会加快查询速度。

pages <---索引(属性)<----查数据。

(1)索引分类

  • 主键索引
  • 普通索引
  • 唯一索引

(2)添加索引

#创建索引
alter table test add index index_name(name);
 	
#创建索引 	
create index index_name on test(name);
 	
#查看索引	
desc table;
 	
#查看索引	
show index from table;
 	
#删除索引	
alter table test drop key index_name;
 	
 	
#添加唯一性索引 	
alter table student add unique key uni_xxx(xxx);
 	
#查看表中数据行数 	
select count(*) from city;
 	
#查看去重数据行数 	
select count(distinct name) from city;

(3)前缀索引和联合索引

  • 前缀索引:根据字段的前N个字符建立索引
alter table test add index idx_name(name(10));

比如对博客的内容进行索引,那么不能将所有的博客内容设置成索引,比较好的办法是将一句话的前几个字设着成索引,这叫做前缀索引

  • 联合索引:多个字段建立一个索引
例:
where a.女生 and b.身高 and c.体重 and d.身材好
index(a,b,c)
特点:前缀生效特性
a,ab,ac,abc,abcd 可以走索引或部分走索引
b bc bcd cd c d ba ... 不走索引

原则:把最常用来做为条件查询的列放在最前面

#创建people表
create table people (id int,name varchar(20),age tinyint,money int ,gender enum('m','f'));
 	
#创建联合索引
alter table people add index idx_gam(gender,age,money);

3.explain详解--慢查询

explain命令使用方法

explain select name,countrycode from city where id=1;

(1)查询数据的方式一:全表扫描:在explain语句结果中type为ALL

什么时候出现全表扫描?

1)业务确实要获取所有数据
2)不走索引导致的全表扫描

为什么会出现不走索引导致的全表扫描

1)没索引
2)索引创建有问题
3)语句有问题

生产中,mysql在使用全表扫描时的性能是极其差的,所以MySQL尽量避免出现全表扫描

(2)查询数据的方式二:索引扫描

2.1 常见的索引扫描类型:

  • 1)index
  • 2)range
  • 3)ref
  • 4)eq_ref
  • 5)const
  • 6)system
  • 7)null

从上到下,性能从最差到最好,我们认为至少要达到range级别

辅助索引的叶子结点存放的是数据的主键值,找到主键值再去聚焦索引里拿到真实数据

image-20221129214141146

4.索引与慢查询优化

知识回顾:数据都是存在硬盘上的,那查询数据不可避免的需要进行IO操作

(1)索引是一种数据结构

索引就是一种数据结构,类似于书的目录。意味着以后再查数据应该先找目录再找数据,而不是用翻页的方式查询数据

(2)索引在MySQL中也叫做“键”

索引在MySQL中也叫做“键”,是存储引擎用于快速找到记录的一种数据结构。

  • primary key
  • unique key
  • index key

注意foreign key不是用来加速查询用的,不在我们研究范围之内,上面三种key前两种除了有加速查询的效果之外还有额外的约束条件(primary key:非空且唯一,unique key:唯一),而index key没有任何约束功能只会帮你加速查询

(3)索引的本质

本质都是:通过不断地缩小想要获取数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是说,有了这种索引机制,我们可以总是用同一种查找方式来锁定数据。

(4)索引的影响:

  • 在表中有大量数据的前提下,创建索引速度会很慢
  • 在索引创建完毕后,对表的查询性能会大幅度提升,但是写的性能会降低

(5)聚集索引(primary key)

聚集索引其实指的就是表的主键,innodb引擎规定一张表中必须要有主键。先来回顾一下存储引擎。

myisam在建表的时候对应到硬盘有几个文件(三个)?

innodb在建表的时候对应到硬盘有几个文件(两个)?frm文件只存放表结构,不可能放索引,也就意味着innodb的索引跟数据都放在idb表数据文件中。

特点:叶子结点放的一条条完整的记录

(6)辅助索引(unique,index)

辅助索引:查询数据的时候不可能都是用id作为筛选条件,也可能会用name,password等字段信息,那么这个时候就无法利用到聚集索引的加速查询效果。就需要给其他字段建立索引,这些索引就叫辅助索引

特点:叶子结点存放的是辅助索引字段对应的那条记录的主键的值(比如:按照name字段创建索引,那么叶子节点存放的是:{name对应的值:name所在的那条记录的主键值})

(7)覆盖索引与非覆盖索引

  • 覆盖索引

只在辅助索引的叶子节点中就已经找到了所有我们想要的数据

select name from user where name='jason';
  • 非覆盖索引

虽然查询的时候命中了索引字段name,但是要查的是age字段,所以还需要利用主键才去查找

select age from user where name='jason';

九、索引数据结构

1.树

索引底层其实是树结构:树是计算机底层的数据结构

(1)树的类型

  • 二叉树、b树、b+树、b*树

二叉树:二叉树里面还可以细分成很多领域,

  • b+树/b*树

只有叶子结点才会存放真实数据,

辅助索引的叶子结点存放的是数据的主键值,找到主键值再去聚焦索引里拿到真实数据

1)B+树

img

只有叶子结点存放真实数据,根和树枝节点存的仅仅是虚拟数据

查询次数由树的层级决定,层级越低次数越少

一个磁盘块儿的大小是一定的,那也就意味着能存的数据量是一定的。如何保证树的层级最低呢?一个磁盘块儿存放占用空间比较小的数据项

思考我们应该给我们一张表里面的什么字段字段建立索引能够降低树的层级高度>>> 主键id字段

2)B*树

img

十、补充

1.测试索引

准备

#1. 准备表
create table s1(
id int,
name varchar(20),
gender char(6),
email varchar(50)
);

#2. 创建存储过程,实现批量插入记录
delimiter $$ #声明存储过程的结束符号为$$
create procedure auto_insert1()
BEGIN
    declare i int default 1;
    while(i<3000000)do
        insert into s1 values(i,'jason','male',concat('jason',i,'@oldboy'));
        set i=i+1;
    end while;
END$$ #$$结束
delimiter ; #重新声明分号为结束符号

#3. 查看存储过程
show create procedure auto_insert1\G 

#4. 调用存储过程
call auto_insert1();
# 表没有任何索引的情况下
select * from s1 where id=30000;
# 避免打印带来的时间损耗
select count(id) from s1 where id = 30000;
select count(id) from s1 where id = 1;

# 给id做一个主键
alter table s1 add primary key(id);  # 速度很慢

select count(id) from s1 where id = 1;  # 速度相较于未建索引之前两者差着数量级
select count(id) from s1 where name = 'jason'  # 速度仍然很慢


"""
范围问题
"""
# 并不是加了索引,以后查询的时候按照这个字段速度就一定快   
select count(id) from s1 where id > 1;  # 速度相较于id = 1慢了很多
select count(id) from s1 where id >1 and id < 3;
select count(id) from s1 where id > 1 and id < 10000;
select count(id) from s1 where id != 3;

alter table s1 drop primary key;  # 删除主键 单独再来研究name字段
select count(id) from s1 where name = 'jason';  # 又慢了

create index idx_name on s1(name);  # 给s1表的name字段创建索引
select count(id) from s1 where name = 'jason'  # 仍然很慢!!!
"""
再来看b+树的原理,数据需要区分度比较高,而我们这张表全是jason,根本无法区分
那这个树其实就建成了“一根棍子”
"""
select count(id) from s1 where name = 'xxx';  
# 这个会很快,我就是一根棍,第一个不匹配直接不需要再往下走了
select count(id) from s1 where name like 'xxx';
select count(id) from s1 where name like 'xxx%';
select count(id) from s1 where name like '%xxx';  # 慢 最左匹配特性

# 区分度低的字段不能建索引
drop index idx_name on s1;

# 给id字段建普通的索引
create index idx_id on s1(id);
select count(id) from s1 where id = 3;  # 快了
select count(id) from s1 where id*12 = 3;  # 慢了  索引的字段一定不要参与计算

drop index idx_id on s1;
select count(id) from s1 where name='jason' and gender = 'male' and id = 3 and email = 'xxx';
# 针对上面这种连续多个and的操作,mysql会从左到右先找区分度比较高的索引字段,先将整体范围降下来再去比较其他条件
create index idx_name on s1(name);
select count(id) from s1 where name='jason' and gender = 'male' and id = 3 and email = 'xxx';  # 并没有加速

drop index idx_name on s1;
# 给name,gender这种区分度不高的字段加上索引并不难加快查询速度

create index idx_id on s1(id);
select count(id) from s1 where name='jason' and gender = 'male' and id = 3 and email = 'xxx';  # 快了  先通过id已经讲数据快速锁定成了一条了
select count(id) from s1 where name='jason' and gender = 'male' and id > 3 and email = 'xxx';  # 慢了  基于id查出来的数据仍然很多,然后还要去比较其他字段

drop index idx_id on s1

create index idx_email on s1(email);
select count(id) from s1 where name='jason' and gender = 'male' and id > 3 and email = 'xxx';  # 快 通过email字段一剑封喉 

2.联合索引

select count(id) from s1 where name='jason' and gender = 'male' and id > 3 and email = 'xxx';  
# 如果上述四个字段区分度都很高,那给谁建都能加速查询
# 给email加然而不用email字段
select count(id) from s1 where name='jason' and gender = 'male' and id > 3; 
# 给name加然而不用name字段
select count(id) from s1 where gender = 'male' and id > 3; 
# 给gender加然而不用gender字段
select count(id) from s1 where id > 3; 

# 带来的问题是所有的字段都建了索引然而都没有用到,还需要花费四次建立的时间
create index idx_all on s1(email,name,gender,id);  # 最左匹配原则,区分度高的往左放
select count(id) from s1 where name='jason' and gender = 'male' and id > 3 and email = 'xxx';  # 速度变快

总结:上面这些操作,你感兴趣可以敲一敲,不感兴趣你就可以不用敲了,权当看个乐呵。理论掌握了就行了

慢查询日志

设定一个时间检测所有超出该时间的sql语句,然后针对性的进行优化!

3.全文检索

MySQL的全文检索功能MYISAM存储引擎支持而InnoDB存储引擎不支持
一般在创建表的时候启用全文检索功能
create table t1(
	id int primary key auto_increment,
  content text
	fulltext(content)
)engine=MyISAM;

# match括号内的值必须是fulltext括号中定义的(单个或者多个)
select content from t1 where match(content) against('jason')
'''上述语句可以用like实现但是查询出来的结果顺序不同 全文检索会以文本匹配的良好程度排序数据再返回效果更佳'''

# 查询扩展
select note_text from productnotes where Math(note_text) Against('jason' with query expansion);
"""
返回除jason外以及其他jason所在行相关文本内容行数据
eg:
	jason is handsome and cool,every one want to be cool,tony want to be more handsome;
	二三句虽然没有jason关键字 但是含有jason所在行的cool和handsome
"""

# 布尔文本搜索
即使没有定义fulltext也可以使用,但是这种方式非常缓慢性能低下
select note_text from productnotes where Match(note_text) Against('jason' in boolean mode);

# 注意事项
1.三个及三个以下字符的词视为短词,全文检索直接忽略且从索引中排除
2.MySQL自身自带一个非用词列表,表内词默认均被忽略(可以修改该列表)
3.出现频率高于50%的词自动作为非用词忽略,该规则不适用于布尔搜索
4.针对待搜索的文本内容不能少于三行,否则检索不返回任何结果
5.单引号默认忽略

4.插入数据

数据库经常被多个用户访问,insert操作可能会很耗时(特别是有很多索引需要更新的时候)而且还可能降低等待处理的select语句性能
如果数据检索是最重要的(一般都是),则可以通过在insert与into之间添加关键字low_priority指示MySQL降低insert语句优先级
	insert low_priority  into 
  
insert还可以将一条select语句的结果插入表中即数据导入:insert select
eg:想从custnew表中合并数据到customers表中
  insert into customers(contact,email) select contact,email from custnew;

5.更新数据

如果使用update语句更新多列值,并且在更新这些列中的一列或者多列出现一个错误会导致整个update操作被取消,如果想发生错误也能继续执行没有错误的更新操作可以采用
	update ignore custmoers ...
  """
  update ignore  set name='jason1',id='a' where id=1;
  	name字段正常修改
  update set name='jason2',id='h' where id=1;
  	全部更新失败
  """

6.关于数据的操作

  • 删除数据
delete语句从表中删除数据,甚至可以是所有数据但是不会删除表本身
并且如果想从表中删除所有的行不要使用delete可以使用truncate速度更快并且会重置主键值(实际是删除原来的表并重新创建一个表而不是逐行删除表中的数据)
  • 主键
查看当前表主键自增到的值(表当前主键值减一)
	select last_insert_id();
  • 外键
MySQL存储引擎可以混用,但是外键不能跨引擎即使用一个引擎的表不能引用具有使用不同引擎表的外键
  • 重命名表
rename关键字可以修改一个或者多个表名
	rename table customer1 to customer2;
  rename table back_cust to b_cust,
  						 back_cust1 to b_cust1,
   						 back_cust2 to b_cust2;

7.事务

MySQL提供两种事务型存储引擎InnoDB和NDB cluster及第三方XtraDB、PBXT

事务处理中有几个关键词汇会反复出现
  事务(transaction)
  回退(rollback)
  提交(commit)
  保留点(savepoint)
		为了支持回退部分事务处理,必须能在事务处理块中合适的位置放置占位符,这样如果需要回退可以回退到某个占位符(保留点)
    创建占位符可以使用savepoint
    	savepoint sp01;
    回退到占位符地址
    	rollback to sp01;
    # 保留点在执行rollback或者commit之后自动释放

8.安全管理

1.创建用户
	create user 用户名 identified by '密码';
 	"""修改密码"""
  	set password for 用户名 = Password('新密码');
    set password = Password('新密码');  # 针对当前登录用户
2.重命名
	rename user 新用户名 to 旧用户名; 
3.删除用户
	drop user 用户名;
4.查看用户访问权限
	show grants for 用户名;
5.授予访问权限
	grant select on db1.* to 用户名;
  # 授予用户对db1数据库下所有表使用select权限
6.撤销权限
	revoke select on db1.* from 用户名;
"""
整个服务器
	grant all/revoke all
整个数据库
	on db.*
特定的表
	on db.t1
"""

9.隔离级别

在SQL标准中定义了四种隔离级别,每一种级别都规定了一个事务中所做的修改
InnoDB支持所有隔离级别
	set transaction isolation level 级别

1.read uncommitted(未提交读)
	事务中的修改即使没有提交,对其他事务也都是可见的,事务可以读取未提交的数据,这一现象也称之为"脏读"
2.read committed(提交读)
	大多数数据库系统默认的隔离级别
  一个事务从开始直到提交之前所作的任何修改对其他事务都是不可见的,这种级别也叫做"不可重复读"
3.repeatable read(可重复读)		# MySQL默认隔离级别
	能够解决"脏读"问题,但是无法解决"幻读"
  所谓幻读指的是当某个事务在读取某个范围内的记录时另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录会产生幻行,InnoDB和XtraDB通过多版本并发控制(MVCC)及间隙锁策略解决该问题
4.serializable(可串行读)
	强制事务串行执行,很少使用该级别

10.锁

读锁(共享锁)
	多个用户同一时刻可以同时读取同一个资源互不干扰
写锁(排他锁)
	一个写锁会阻塞其他的写锁和读锁
死锁
	1.多个事务试图以不同的顺序锁定资源时就可能会产生死锁
  2.多个事务同时锁定同一个资源时也会产生死锁
	# Innodb通过将持有最少行级排他锁的事务回滚

11.事务日志

事务日志可以帮助提高事务的效率 
	存储引擎在修改表的数据时只需要修改其内存拷贝再把该修改记录到持久在硬盘上的事务日志中,而不用每次都将修改的数据本身持久到磁盘
  事务日志采用的是追加方式因此写日志操作是磁盘上一小块区域内的顺序IO而不像随机IO需要次哦按的多个地方移动磁头所以采用事务日志的方式相对来说要快的多
  事务日志持久之后内存中被修改的数据再后台可以慢慢刷回磁盘,目前大多数存储引擎都是这样实现的,通常称之为"预写式日志"修改数据需要写两次磁盘

12.MVCC多版本控制

MVCC只能在read committed(提交读)、repeatable read(可重复读)两种隔离级别下工作,其他两个不兼容(read uncommitted:总是读取最新  serializable:所有的行都加锁)

InnoDB的MVCC通过在每行记录后面保存两个隐藏的列来实现MVCC
	一个列保存了行的创建时间
  一个列保存了行的过期时间(或删除时间)  # 本质是系统版本号
每开始一个新的事务版本号都会自动递增,事务开始时刻的系统版本号会作为事务的版本号用来和查询到的每行记录版本号进行比较

例如
刚插入第一条数据的时候,我们默认事务id为1,实际是这样存储的
    username		create_version		delete_version
    jason						1					
可以看到,我们在content列插入了kobe这条数据,在create_version这列存储了1,1是这次插入操作的事务id。
然后我们将jason修改为jason01,实际存储是这样的
    username		create_version		delete_version
    jason						1									2
    jason01					2
可以看到,update的时候,会先将之前的数据delete_version标记为当前新的事务id,也就是2,然后将新数据写入,将新数据的create_version标记为新的事务id
当我们删除数据的时候,实际存储是这样的
		username		create_version		delete_version
    jason01					2									 3
"""
由此当我们查询一条记录的时候,只有满足以下两个条件的记录才会被显示出来:
   1.当前事务id要大于或者等于当前行的create_version值,这表示在事务开始前这行数据已经存在了。
   2.当前事务id要小于delete_version值,这表示在事务开始之后这行记录才被删除。
"""

13.转换表的引擎

主要有三种方式,并各有优缺点!
# 1.alter table
	alter table t1 engine=InnoDB;
  """
  	适用于任何存储引擎 但是需要执行很长时间 MySQL会按行将数据从原表赋值到一张新的表中,在复制期间可能会消耗系统所有的IO能力,同时原表会加读锁
  """
# 2.导入导出
	"""
	使用mysqldump工具将数据导出到文件,然后修改文件中相应的SQL语句
		1.引擎选项
		2.表名
	""" 	
# 3.insert ... select
	"""
	综合了第一种方案的高效和第二种方案的安全
		1.先创建一张新的表
		2.利用insert ... select语法导数据
	数据量不大这样做非常合适 数据量大可以考虑分批处理 针对每一段数据执行事务提交操作避免产生过多的undo
	"""
  ps:上述操作可以使用pt-online-schema-change(基于facebook的在线schema变更技术)工具,简单方便的执行上述过程

、数据库三大范式

。。。

第一范式(1NF):属性不可分割,即每个属性都是不可分割的原子项。(实体的属性即表中的列)

第二范式(2NF):满足第一范式;且不存在部分依赖,即非主属性必须完全依赖于主属性。(主属性即主键;完全依赖是针对于联合主键的情况,非主键列不能只依赖于主键的一部分)

第三范式(3NF):满足第二范式;且不存在传递依赖,即非主属性不能与非主属性之间有依赖关系,非主属性必须直接依赖于主属性,不能间接依赖主属性。(A -> B, B ->C, A -> C)

posted @ 2022-11-29 21:59  Duosg  阅读(76)  评论(0编辑  收藏  举报