SQL注入问题,视图,触发器,事务,流程控制,索引,慢查询优化
在nevicat中新建一个表格
pycharm连接数据库
import pymysql conn = pymysql.connect( host='127.0.0.1', port=3306, user='root', password='123', db='db6', charset='utf8mb4', autocommit = True ) cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) # 1.获取用户名和密码 username = input('username>>>:').strip() password = input('password>>>:').strip() # 2.构造查询SQL语句 sql = "select * from userinfo where name='%s' and pwd ='%s'" %(username,password) # 3.发送给服务器执行SQL语句 cursor.execute(sql) # 4.获取执行结果 res = cursor.fetchall() if res: print(res) else: print('用户名或密码错误')
怪像1: 输对用户名就可以登录成功
怪像2:不需要对的用户名和密码也可以登录成功
上述现象叫做SQL注入——利用特殊符合的组合产生特殊的含义,从而避开正常的业务逻辑。
针对上述的SQL注入问题,核心在于手动拼接了关键数据,交给execute处理即可
executemany(sql,[(),(),(),()...])
将上述第2步和第3步代码进行如下修改:
通过代码向数据库表中插入数据
# 向数据表中插入数据 sql ="insert into userinfo(name,pwd) values(%s, %s)" cursor.execute(sql,(username,password)) # 插入一条数据 cursor.executemany(sql,[('oscar','444'),('lily','555'),('lucy','666')]) # 一次性插入多条数据
create view teacher2course as select * from teacher inner join course on teacher.tid = course.teacher_id;
注意事项
- 视图的表只能用来查询,不能做其他增删改操作
- 视图尽量少用,会跟真正的表产生混淆,从而干扰操作者
达到某个条件之后自动触发执行。
在MySQL中更加详细的说明是触发器:针对表继续增、删、改操作能够自动触发
主要有六种情况:增前、增后、删前、删后、改前、改后
create trigger 触发器的名字 before/after insert/update/delete on 表名 for each row begin sql语句 end
1. 触发器命名有一定的规律
tri_before_insert_t1
tri_after_delete_t2
tri_after_update_t2
2. 临时修改SQL语句的结束符,因为有些操作中需要使用分号
delimiter $$ # 将mysql默认的结束符由;换成$$
触发器实际应用
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;
事务的四大特性(ACID)
A: 原子性
事务中的各项操作是不可分割的整体 要么同时成功要么同时失败
C: 一致性
使数据库从一个一致性状态变到另一个一致性状态
I: 隔离性
多个事务之间彼此不干扰
D: 持久性
也称永久性,指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的
案例
# 创建一个表
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;
因为上述修改操作只是修改了内存中的数据,还没有更新到硬盘,所以可以使用rollback回到之前状态。
开启事务之后,只要没有执行commit操作,数据其实都没有真正刷新到硬盘。
# 确认操作
commit;
事务相关关键字
start transaction; # 开启事物 rollback # 回滚到之前状态 commit # 确认 savepoint # 节点(很少用)
事物隔离级别
InnoDB支持所有隔离级别
set transaction isolation level 级别
在SQL标准中定义了四种隔离级别,每一种级别都规定了一个事务中所做的修改。
1. read uncommitted(未提交读)
事务中的修改即使没有提交,对其他事务也都是可见的,事务可以读取未提交的数据,这一现象也称之为"脏读"
2. read committed(提交读)
大多数数据库系统默认的隔离级别
一个事务从开始直到提交之前所作的任何修改对其他事务都是不可见的,这种级别也叫做"不可重复读"
3. repeatable read(可重复读) # MySQL默认隔离级别
能够解决"脏读"问题,但是无法解决"幻读"
所谓幻读指的是当某个事务在读取某个范围内的记录时另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录会产生幻行,InnoDB和XtraDB通过多版本并发控制(MVCC)及间隙锁策略解决该问题
4. serializable(可串行读)
强制事务串行执行,很少使用该级别
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值,这表示在事务开始之后这行记录才被删除。
"""
delimiter $$ create procedure p1() begin select * from cmd; end $$ delimiter ; # 调用 call p1()
有参
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; 查看 call p1(1,5,@res) 调用 select @res 查看
查看存储过程具体信息
show create procedure pro1;
查看所有存储过程
show procedure status;
删除存储过程
drop procedure pro1;
# 大前提:存储过程在哪个库下面创建的只能在对应的库下面才能使用!!!
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;')
常用函数
# 1.移除指定字符 Trim、LTrim、RTrim # 2.大小写转换 Lower、Upper # 3.获取左右起始指定个数字符 Left、Right # 4.返回读音相似值(对英文效果) Soundex """ eg:客户表中有一个顾客登记的用户名为J.Lee 但如果这是输入错误真名其实叫J.Lie,可以使用soundex匹配发音类似的 where Soundex(name)=Soundex('J.Lie') """

# 5.日期格式:date_format(重要) '''在MySQL中表示时间格式尽量采用2022-11-11形式''' 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计算两个日期差值
declare i int default 0; IF i = 1 THEN SELECT 1; ELSEIF i = 2 THEN SELECT 2; ELSE SELECT 7; END IF;
循环结构
DECLARE num INT ; SET num = 0 ; WHILE num < 10 DO SELECT num ; SET num = num + 1 ; END WHILE ;
1)索引就好比一本书的目录,它能让你更快的找到自己想要的内容
2)让获取的数据更有目的性,从而提高数据库检索数据的性能
索引在MySQL中也叫做“键”,是存储引擎用于快速找到记录的一种数据结构
* primary key
* unique key
* index key
- 上述的三个key都可以加快数据查询
- primary key和unique key除了可以加快查询,本身还自带限制条件,而index key很单一就是用来加快数据查询
- 外键不属于索引键的范围,是用来建立关系的,与加快查询无关
索引加快查询的本质
id int primary key auto_increment, name varchar(32) unique, province varchar(32) age int phone bigint select name from userinfo where phone=18818888888; # 一页页的翻 select name from userinfo where id=99999; # 按照目录确定页数找
注意:
索引可以加快数据查询,但是会降低增删的速度
通常情况下我们频繁使用某些字段查询数据,为了提升查询的速度可以将该字段建立索引
聚集索引(primary key) 主键、主键索引 辅助索引(unique,index) 除主键意外的都是辅助索引 覆盖索引 select name from user where name='jason'; 非覆盖索引 select age from user where name='jason';
索引底层其实是树结构>>>:树是计算机底层的数据结构
树有的类型
二叉树、b树、b+树、B*树......
二叉树
二叉树里面还可以细分成很多领域,我们简单的了解即可
二叉意味着每个节点最大只能分两个子节点
B树
所有的节点都可以存放完整的数据
B+\*树
只有叶子节点才会存放真正的数据,其他节点只存放索引数据
B+叶子节点增加了指向其他叶子节点的指针
B*叶子节点和枝节点都有指向其他节点的指针
辅助索引在查询数据的时候最后还是需要借助于聚集索引
辅助索引叶子节点存放的是数据的主键值
有时候就算采用索引字段查询数据 也可能不会走索引!!!
最好能记三个左右的特殊情况
explain 1)index 尽量避免 2)range 3)ref 4)eq_ref 5)const 6)system 7)null
#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字段一剑封喉
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'; # 速度变快
总结: 上面这些操作,你感兴趣可以敲一敲,不感兴趣你就可以不用敲了,权当看个乐呵。理论掌握了就行了
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.单引号默认忽略
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人