面试常见题目汇总
投简历网站
拉勾招聘,前程无忧,boss,等
.
.
.
.
.
.
git 使用注意事项
git账号密码
邮箱:965493842@qq.com
账号:tengjincheng 密码:tengyifan888
从别人的仓库clone下来的代码,你要推到你远程仓库,要先把远程仓库的地址,
改成你要推的你的远程的空仓库的地址
或者你直接把别人的代码forked 你的仓库里,然后你再从你的仓库里面复制clone地址
到你的本地克隆下来,添加代码后,就可以直接提交了,直接提交到你的远程仓库了
就和别人仓库里面的代码没关系了
---------------------------------------------------
如果是一个本地的项目,想要推到你的远程gitee仓库去,要先建出一个空仓库出来
复制对应的网址
本地仓库往上推前,要先设置远程仓库,才能推到远程仓库
git remote add teng https://gitee.com/tengjincheng/luffy_api.git
# teng是 你给远程仓库起的名字 后面的网址就是该仓库名要绑定的远程仓库的网址
git push 远程仓库名字 对应的分支名字
就把本地对应分支的代码,推到远程仓库对应分支上去了
---------------------------------------------------
---------------------------------------------------
.
.
.
git 命令汇总
# 常见git命令
git init # 初始化仓库
git status # 查看文件变化 (只能看暂存区和工作区)
git add . # 当前路径下所有文件变更都提交
git commit -m '注释' # 把暂存区所有内容,提交到版本库
git reset --hard 版本号 # 回到某个版本
git branch # 查看分支
git branch 分支名 # 创建分支
git checkout dev # 切换到dev分支
先来到master分支: git checkout master
执行合并命令:git merge dev # 把本地dev合并到我本地(master)上
# ( 也可以本地的dev分支往远程的dev上推,在远程点pull Requests 也可以合并 )
git remote add 远程仓库名字 仓库网址 # 设置远程仓库的名字
git push 远程仓库名字 对应的分支名字 # 将本地对应分支的代码,推到远程仓库对应分支
git clone 网址 # 把远程仓库的代码拉到本地仓库
# 还可以用pycharm 点击vcs,点击get from version control ,
# 把远程仓库网址粘贴到url里面 点击clone 也可以直接克隆下来
-----------------------------
# 出现冲突的原因
1 多人在同一分支开发冲突, 一般发生在将远程仓库代码pull拉下来的时候
2 分支合并出现冲突,自己确定要保留主的还是分支的,再提交代码就行了
# 首先为什么要pull往下拉代码,因为你一开始从远程仓库克隆的代码
# 在加代码的过程中,可能别人也在一直往master主分支里面贡献代码
# 当你想将代码提交到远程仓库的时候,可能master已经被别人升了好几版了
# 这个时候你的master已经不是最新的了,你推不上去的!!!
# 所以要你先将远程仓库最新的代码pull下来,自动与自己的代码合并
# 然后你才能再往远程的仓库上推!!!
你如果想少出现这种冲突,勤拉取代码 git pull origin master或者dev
保证自己代码是最新的
分支合并出现冲突,自己确定要保留主的还是分支的,再提交代码就行了
----------------------------------------------------
# 来到公司后,领导给把你拉到了公司的git仓库后,你大致要做的事情
1. 先从仓库里面把项目克隆到本地的dev分支上
2. 安装项目所需的依赖模块
3. 运行起项目文件
4. 编写代码,提交到远程仓库,如果有冲突解决冲突
----------------------------------------------------
.
.
.
.
sql语句查询
# 1
# 给定一个员工表(employes)和一个部门表(departments),编写-个SQL查询语句,
# 列出每个部门的部门名称department_name, 及该部门的员工数量(employee count)。
SELECT d.department_name, COUNT(e.employee_id) AS employee_count
FROM departments AS d
LEFT JOIN employees AS e ON d.department_id = e.department_id
GROUP BY d.department_name;
# 为什么要以部门表为基准,去左连接员工表,目的就是连表后,
# 让没有绑定部门的员工数据不显示,两表连接后,再按部门分组
# 最后求每一个部门分组里面的员工数量
--------------------------------------------------------------------
# 2 查询所有学生的所有科目的所有成绩
先理下最合理的表结构,学生表放学生的一些字段,比如年龄性别等。课程表里放课程的名称,老师姓名等信息
最后分数表里面放 学生id 课程id 分数等信息
select Student.name,Course.name,Grade.score
from Student
inner join Grade on Student.id = Grade.student_id
inner join Course on Course.id = Grade.Course_id
# 3 查询所有学生的学号、姓名、选课数、总成绩
SELECT student.学号, name, count(score) AS class_num ,SUM(score) AS total_score
FROM score LEFT JOIN student ON( student.学号 = score.学号)
GROUP BY score.学号
ORDER BY SUM(score) DESC
# 以分数表作为基准,左连接学生表,大表里面就会去掉没有成绩的学生了
# 再以分数表表里面的学号分组,对分数计数,对分数求和
.
.
.
.
.
django的orm语句
查询年龄大于18的用户数据
# res = models.User.objects.filter(age__gt=28)
# res = models.User.objects.filter(age__gt=28).values('name','age')
# print(res)
大于等于__gte 小于等于__lte equal 等于
# res = models.User.objects.filter(age__gte=18)
# res = models.User.objects.filter(age__lte=38)
查询年龄是18或者28或者38的数据 __in=() 成员运算
# res = models.User.objects.filter(age__in=(18, 28, 38))
查询注册年份是2022年的数据 __year=2022
# res = models.User.objects.filter(register_time__year=2022)
针对一对多 插入数据可以直接填写表中的实际字段
models.Book.objects.create(title='三国演义', price=888.88, publish_id=1)
针对一对多 插入对象也可以填写表中的类中字段名
publish_obj = models.Publish.objects.filter(pk=1).first()
models.Book.objects.create(title='水浒传', price=555.66, publish=publish_obj)
所以如果等于号前面是表中的实际字段,那等于号后面就要写一个具体的数数据!!!
如果等于号前面是模型表中的字段名(也就是没自动加_id前的样子),那等于号后面就要写一个对应的对象!!!
------------------------------------
注意:当对象点虚拟的外键字段名的时候,比如书籍对象点虚拟外键字段authors 时:
如果如果后面跟的是 add set remove clear 则是操作第三张虚拟关系表!!!
当你使用其他方式 比如 all filter 等的时候则是操作的是 对应的作者表Author!!!
只有对象点字段名后跟的是add set remove clear 才会跳到对应的那张虚拟表去!!
-------------------------------------
# 跨表查询
mysql的跨表查询有两种方法,一种是子查询,一种是连表操作 ,ORM也类似
正向查询:由外键字段所在的表数据查询关联的表数据 正向查询
谁手上有外键字段,查别人就是正向查询!!!
---------------------------------
ORM跨表查询的口诀(极其重要!!!)
正向查询按外键字段名
反向查询按表名小写加_set
---------------------------------
# ORM的跨表查询分两种,
一种是基于对象的跨表查询,通过点的方式
正向查询按外键字段名
反向查询按表名小写加_set
---------------------------------------------------
一种是基于连表的跨表查询,通过__杠杠的方式
正向查询按外键字段名
反向查询按表名小写(没有_set!!!) 别搞混了!!!
# 基于对象的跨表查询
1.查询主键为1的书籍对应的出版社名称 正向查询
book_obj = models.Book.objects.filter(pk=1).first()
print(book_obj.publish.name)
2.查询主键为4的书籍对应的作者姓名 正向查询
book_obj = models.Book.objects.filter(pk=4).first()
print(book_obj.authors.all()) # QuerySet[作者对象1,作者对象2]
print(book_obj.authors.all().values('name'))
3.查询jason的电话号码 正向查询
author_obj = models.Author.objects.filter(name='jason').first()
print(author_obj.author_detail.phone)
查询北方出版社出版过的书籍 # 反向查询
publish_obj = models.Publish.objects.filter(name='北方出版社').first()
print(publish_obj.book_set.all()) # Queryset[书对象1,书对象2,]
print(publish_obj.book_set.all().values('name'))
查询jason写过的书籍
author_obj = models.Author.objects.filter(name='jason').first()
print(author_obj.book_set.all()) # Queryset[书对象1,书对象2,]
print(author_obj.book_set.all().values('name'))
-------------------------------------------------
# 基于连表的跨表查询
1.查询主键为1的书籍对应的出版社名称 # 正向查询
res = models.Book.objects.filter(pk=1).values('publish__name','title')
print(res) # 结果是个queryset列表里面套了一个字典,对应value里面的字段信息
res = models.Book.objects.filter(pk=1).values('publish__name','title').first()
print(res) # 就直接是个字典了
values()括号里面就是要写字段名,可以写自己表里面的字段名,也可以写关联表里面的字段名!!
通过在书表里面的外键字段publish,拿关联表里面的对于name字段的数据
# 而且要注意最后字典里面的键是'publish__name' values里面写啥,字典里键就是啥!!!
.
.
.
.
当在浏览器输入一个地址,按下回车发生了什么
# 1 将域名地址解析为 ip 地址。 先从本地dns,没有再到根域名服务器,再没有再往上找
# 2 TCP 三次握手 三次握手的目的是为了确认双方的接收能力和发送能力是否正常。
# 3 发送请求 页面将请求内容以请求报文形式发送给服务器,由服务器分析处理。
# 4 返回数据 当页面请求发送到服务器端后,服务器端会返回一个 html 文件作为响应
# 5 页面渲染 浏览器会根据 html 文件 渲染出页面出来
# 6 TCP 四次挥手
四次挥手的原因是因为 TCP 的连接是全双工的,所以需要双方分别释放到对方的连接,单独一方的连接释放,只代表不能再向对方发送数据
.
.
.
.
.
.
.
is” 和“==”的区别
Python中对象包含的三个基本要素,分别是:id(身份标识)、type(数据类型)和value(值)。
== 是用来比较对象的数值(内容)的
is 是用来比较对象的身份的(内存地址的) 它是以id来判断的
.
python 小整数池
# 作用:节省内存,提高解释器执行效率
数值比较小的整数,可能在程序中会非常频繁地使用,就需要频繁的申请内存空间
为了避免因创建相同的值,而重复申请内存空间所带来的效率问题,使用了小整数池技术。
不同类里面,变量的值只要一样,内存地址就一样
如果超过[-5,256]范围外,不同类里面,变量的值一样,内存地址也不一样,会重新申请内存的
但是pycharm有优化,扩大了小整数池的范围了
字符串也创了一个池,值在池范围内的,即使在不同类里面,内存地址也是一样的!!!
具体范围不知道
.
.
.
.
什么叫偏函数 /'pɑrʃəl/
# 通过partial类实例化,传入原函数与原函数的部分参数,此时生成的对象可以看出是偏函数
# 然后再通过对象加括号,触发双下call方法,传入剩余参数 所得结果
# 和原函数一次性传所有值运行的结果一样
# 偏函数作用: 实现原函数分步传值的效果
from functools import partial
def add(a,b,c):
return a+b+c
add888=partial(add,2)
print(add888(3,4))
.
.
.
字符编码理论
# 最开始美国的码 1个字节的二进制数字表示一个字符
ASCII码:记录了英文字母跟数字的对应关系 用8bit(1字节)来表示一个英文字符
----------------------
# 后来各国自己的码 GBK 2个字节的二进制数字表示一个字符
比如中国的GBK码:用至少(2字节)来表示一个字符
----------------------
# 全球通用的码
unicode万国码是字符的一种编码方式 所有的语言的每一个字符都有一个对应的数字
所有语言的字符都有一个唯一的Unicode编号
可变长字符编码系列 utf8(常用) utf16
解码的时候,如何知道是按一个字节还是两个字节,还是三个字节解码?
每一个字节开头有个标志位
0 代表1个字节 对应一个字符
110 代表两个字节 对应一个字符
1110 代表三个字节 对应一个字符
所以只要在解码的时候,先根据开头的数字,判断出要解码字符是用几个字节组成的
然后再根据后面对应的数字去解码
----------------------------------------------------
# utf-8由于它的长度不固定,使得在内存处理字符变得复杂。
解决策略是:
在内存中存储字符时还是使用unicode编码,内存处理方便。
而在文件的存储中,则使用utf-8编码,可以压缩内存,节省空间。
即从文件中读取utf-8编码到内存时,会自动转换为unicode编码,
而从内存中将字符保存到文件时,则自动转换为utf-8编码。
----------------------------------------------------
# unicode是字符的一种编码方式,但没规定变成的二进制数字怎么存储
# utf-8是unicode的具体存储形式!!!
# 用4字节形式表示一个字符 4字节形式也称UTF-32 是静态的,而且是固定长度的
# 但用4个字节来表示一个字符,很浪费内存,这也是为什么会有utf8的原因!!!
如果第一个字节(leading byte)的最高三位是110,那么表示这个字符占2个字节,第二个字节的最高2位是10
蓝色部分的数字组合在一起,就是实际的码位值。
.
假如要表示的字符,其码位值是413,那么就表示如下,
.
.
.
vue常见语法总结
.
.
.
#1 面试官要看你项目
- 编码水平
- 公司的看不了,给他看的,全是个人项目
- 公司项目看不了,签了保密协议
#2 数据库如何处理的?
-云数据库:阿里云数据库,花钱,买服务---》账号和密码---》公司不需要自己搭建mysql
-mysql
-redis
-mongodb
-自己的数据库,部署在云服务器上的数据库,是你自己的
# 3 你用过什么云产品?
- 阿里云的ecs,服务器
- 阿里云的oss ,对象存储
- 云短信
- 七牛云 文件存储
# 4 看看你的数据库
-配置文件 dev.py 连的是本地127.0.0.1
-你们上线怎么弄
-我不知道,就是给我一个地址,端口,用户名密码
- 上线的数据库服务和项目服务 是在同一台及其上吗?
# 5 celery用过
定时任务
定时
异步任务
# 6 多线程用过
-用过
-怎么用的?两种方式
-类实例化得到对象Thread类 传入target 任务函数,对象.start
-写一个类,继承Thread,重写run方法,写任务 类实例化得到对象 对象.start
-如果io密集型,用多线程,计算密集型用多进程 ---》只针对于cpython
# 7 mysql 用过,索引你知道吗?
唯一索引和联合索引有什么区别
#8 1千万w,性别字段(男女),查询时候,这个字段要不要加索引
-因为它就两种状态,建立索引是没用的,即便建立索引,也不会走
# 9 实现踢下线
-session机制---》表中把它那条记录删除
-token机制----》
-下线人id----》放个位置
-进入到认证类中---》
# 10 实现黑名单功能
-建立个黑名单表
-id 用户id ,ip,时间。。。
.
.
.
什么是 深浅拷贝
# 浅拷贝 是会将对象的每个属性进行依次复制,
当对象的属性值是不可变类型时,复制的是对象的值
但是当对象的属性值是可变类型时,复制的是对象的引用
# 深拷贝 会复制出一个一摸一样的对象,新对象和原对象不共享内存,修改新对象不会改变原对象。
所以 对于不可变类型来说,深浅拷贝都是一样的,重新复制一份原数据,和原对象不共享内存地址
对于可变类型来说,
深拷贝还是完全复制出一个新的数据与原数据不共享内存地址
浅拷贝则是复制的是引用,与原数据共享内存地址!!!
----------------------------------------------
# 双下new与双下init的区别
new 是类加括号初始化的时候,生成一个空对象
init 是类加括号初始化生成一个空对象,并在将对象实例化的时候,触发
----------------------------------------------
# python的可变数据类型与不可变数据类型是什么
可变数据类型是数据值发生变化,内存地址不变 例如 字典,列表,集合,及文件对象
不可变数据类型是 数据值发生改变,其内存地址发生改变 例如 整型,浮点型,字符串,元组,布尔
----------------------------------------------
# 什么是生成器? 什么应用场景
函数内部写有yield关键字,调用该函数时,不会运行该函数的代码,会返回一个生成器。
需要用该生成器点next()方法,才能运行该生成器,并在运行到yield时,会暂停,
并返回当前的迭代值,当生成器再次点next()方法时,会从原来 暂停 的地方继续执行,
直到再次遇到 yield 并返回此次的迭代值
实现的效果就是,一边循环一边产生数据。
生成器的作用,可以获得庞大的数据,同时占用较小的内存。
.
.
.
什么是闭包函数?
def outer():
x=1
def inner():
print(x)
return inner
# 定义在函数体内部的函数,并且该内部的函数对外部名称空间的变量有引用
# 该函数就叫闭包函数
---------------------------------------------------
# 1 数据库三大范式是什么
第一范式(1NF)是指在关系模型中,数据库表的每一列都是不可分割的原子数据项,
第一范式主要是保证数据表中的每一个字段的值必须具有原子性,每个字段的值是不可再拆分的最小数据单元
第二范式(2NF)的要求是满足第一范式,且非主键字段,必须完全依赖于主键,
如果表是复合主键,非主键字段不能仅依赖主键的一部分
第三范式(3NF)的要求是满足第二范式,且表中的非主键列必须和主键直接相关而不能间接相关。
也就是说数据表中的所有非主键字段不能依赖于其他非主键字段.
这个规则的意思是所有非主属性之间不能有依赖关系,它们是互相独立的
------------------------------------------------------
范式的优缺点:
优点:数据的标准化有助于消除数据库中的数据冗余
缺点:
1 降低了查询效率,因为范式等级越高,设计出来的表就越多,进行数据查询的时候就可能
需要关联多张表,不仅代价昂贵,而且可能会使得一些索引失效
2 范式只是提出设计的标标准,实际设计的时候,我们可能为了性能和读取效率违反范式的原则,
通过增加少量的冗余或重复的数据来提高数据库的读取性能,减少关联查询,实现空间换时间的目的
------------------------------------------------------
第一范式(1NF)
.
.
.
第二范式(2NF)
.
.
第三范式(3NF)
.
.
.
.
.
事务的特性和隔离级别
事务的四大特性:acid a原子性,c一致性,i隔离性,d持久性
# 原子性(Atomicity):数据库把“要么全做,要么全部做”的这种规则称为原子性
# 隔离性(Isolation):事务之间相互隔离,不受影响,这与事务的隔离级别密切相关
# 一致性(Consistency):事务执行前后的状态要一致,可理解为数据一致性
# 持久性(Durable):事务完成之后,她对数据的修改是永恒的,即时出现故障也能够正常保持
当在执行事务的时候,在未提交的状态下,只是改了内存的数据,硬盘的真实的数据还没改了!!
一旦执行了事务的二次确认,会将内存中改的数据同步到硬盘中,
---------------------------------------------
隔离级别:
1. read uncommitted(未提交读,或者叫读未提交)
读未提交隔离级别:事务B读取到了事务A未提交的数据
导致:脏读:读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,
也就是可能最终不会存到数据库中,也就是不存在的数据。
读到了并一定最终存在的数据,这就是脏读
---------------------------
2. read committed(提交读,或读已提交)----大多数数据库系统默认的隔离级别
读已提交隔离级别:
一个事务从开始直到提交之前对数据库所作的任何修改,对其他事务都是不可见的。
导致:
不可重复读:在一个事务内,最开始读到的数据,和事务结束前的任意时刻
读到的同一批数据出现不一致的情况。
原因是:在第一个事务中的两次读数据之间,由于第二个事务提交了对于数据的修改
----------------------------
3. repeatable read(可重复读) ---------MySQL默认隔离级别
可重复读隔离级别:
事务A在读到一条数据之后,此时事务B对该数据进行了修改并提交,
那么事务A再读该数据,读到的还是原来的内容。
会导致幻读:
幻读 一个事务在执行时,另一个事务插入了一条数据,从而导致第一个事务操作
完成之后发现结果与预想的不一致,跟产生了幻觉一样。
解决办法:mvcc机制加间隙锁
# 间隙锁 假如 id=6的数据不存在,那么该查询语法就锁住了id=6所在的间隙了
# 当对一个不存在的数据加锁后,默认就是锁定前后两条数据之间的区间
# 当其他事务再尝试向该区间插入数据时,就会陷入阻塞,
# 只有当持有间隙锁的事务结束后,才能继续执行插入操作。
select * from `users` where id = 6 lock in share mode;
----------------------------
4. serializable(串行读) -------不会产生脏读与幻读,但是执行效率很低。
强制事务串行执行,很少使用该级别,会导致执行效率很低
.
.
.
.
.
.
mysql有哪些索引类型,分别有什么作用
# 正确答案:
# 聚簇索引,聚集索引,主键索引,主键,如果不存在主键,隐藏一个主键,构建聚簇索引
# 辅助索引,普通索引 index
# 唯一索引 unique
# 联合索引,组合索引,多列索引:unique_to
.
.
.
.
.
.
.
.
.
补充 auth表是怎么加密密码,与校验明文密码是否正确的?
django 的auth user表,密码是加密的,即便的同样的密码,密文都不一样
-每次加密,都会随机生成一个盐
# 比如 auth表中的用户密码为
pbkdf2_sha256$260000$B9ZRmPFpWb3H4kdDDmgYA9$CM3Q/ZfYyXzxwvjZ+HOcdovaJS7681kDsW77hr5fo5o=
pbkdf2_sha256 加密方式
260000 固定数字
B9ZRmPFpWb3H4kdDDmgYA9 随机生成的盐
CM3Q/ZfYyXzxwvjZ+HOcdovaJS7681kDsW77hr5fo5o=
这个是用注册的密码和盐拼在一起然后用加密算法生成的字符串
当登录的时候,用户提交用户名与密码过来后,从数据库表里面拿出该用户的盐与提交过来的密码合在一起
,然后用加密算法再生成字符串,然后比较生成的字符串 与 数据库里面的该加密字符串
CM3Q/ZfYyXzxwvjZ+HOcdovaJS7681kDsW77hr5fo5o= 进行比较,对了就确认用户密码输入正确
.
.
.
.
.
小补充:
静态资源的存放路径比如:
<img src="/static/123.png" alt="">
static 前面一定不要忘了/ 斜杠 有了斜杠后该图片的绝对路径就变成了
http://127.0.0.1:5000/static/123.png ip端口加相对路径了
static前面不加斜杠会导致,可能会导致图片无法正常显示!!!
.
.
.
.
什么是qps,tps,并发量,pv,uv
# qps:
Queries Per Second,每秒查询率,一台服务器每秒能够响应的查询次数
--------------------------------------
# tps:
Transactions Per Second,是每秒处理的事务数
该事务 包括一条消息入和一条消息出,加上一次用户数据库访问,整体叫一个事务数
--------------------------------------
# 并发量:
系统每秒钟可以处理的请求或事务的数量,反应了系统的负载能力
--------------------------------------
# PV(Page View):
页面访问量,即页面浏览量或点击量,用户每次刷新即被计算一次。
可以统计服务一天的访问日志得到
--------------------------------------
# UV(Unique Visitor):
独立访客,统计1天内访问某站点的用户数。
可以统计服务一天的访问日志并根据用户的唯一标识去重得到
--------------------------------------
# DAU(日活):
DAU(Daily Active User),日活跃用户数量。
常用于反映网站、app、网游的运营情况。DAU通常统计一日(统计日)之内,
登录或使用了某个产品的用户数(去除重复登录的用户),与UV概念相似
--------------------------------------
# MAU(Month Active User):
月活跃用户数量,指网站、app等去重后的月活跃用户数量
.
.
.
.
.
.
什么是接口幂等性问题,如何解决?
# 幂等性:
多次操作,结果或者说效果是一致的
# 接口幂等性:无论调用多少次接口,产生的效果是一样的
get请求 获取数据 天然幂等
put请求 修改数据 天然幂等
delete请求 删除数据 天然幂等
post请求 新增数据,会出现不幂等的情况,需要把它做成幂等性的
-------------------------------------------------
# 解决办法:
1 唯一主键 unique 新增用户,用户名唯一,但是订单问题解决不了
2 token机制,随机字符串
在使用新增数据接口之前,先发送请求到后端获取到token,
比如在页面加载出来的时候,就先发送请求到后端,拿到token,
并且同时后端服务器,保存该生成的token到redis数据库
所以只要在该页面,调用新增接口,就让请求头携带随机字符串token,到后端,
校验这个token在不在我们保存的redis里面,如果在,说明正常,
删除token,允许新增。
如果不在,说明新增操作已经执行过一次了,redis中的token串已经被删掉了
就不允许新增!!!
3 前端:按钮只能点击一次
.
.
.
.
.
什么是 GIL锁 ?
1 什么是GIL锁 ?
Global Interpreter Lock 全局解释器锁
它的本质就是一个大的互斥锁,它是cpython的一个机制,GIL只存在于cpython解释器中
它限制了同一个进程下,线程只有获取到了gil锁,才能执行,如果没有拿到gil锁,线程是不能执行的
2 gil锁的作用是什么 ?
使得在同一进程内,任何时刻仅有一个线程在执行
-------------------------------------------------------------------
3 为什么要有GIL锁 ?
不是为了保证数据安全的, 保证数据安全用互斥锁就可以了,gil锁不能保证数据安全!!
python的垃圾回收机制,需要用一条垃圾回收的线程来做!!!
如果同一时刻,一个进程下,有多个线程在同时执行,
其他线程刚创建的数据还没来得及绑定变量,就可能被垃圾回收线程给回收掉 !!!
所以用gil锁保证在同一进程内,任何时刻仅有一个线程在执行
但后来随着多核的出现,导致python的多线程并不能利用多核
-------------------------------------------------------------------
.
.
.
.
python的垃圾回收机制是什么样的 ?
1 python的垃圾回收机制是什么样的?
-高级一点的语言,为了保证内存的使用效率,都会有垃圾回收的机制。
而python使用以下三种方式来做垃圾回收
-----------------------------------------------------
# 引用计数
-有多少个变量名指向它,它的引用计数就为几,
-当引用计数为0,说明没有变量名指向它了,这块内存空间就会被回收掉
-引用计数存在的问题:循环引用问题,内存空间里面还有指向其他内存空间的变量名,导致无法回收
# 标记清除
-是为了解决引用计数存在的循环引用问题的
-第一阶段是标记阶段,它会把所有的『活动对象』打上标记,第二阶段是把那些没有标记的对象『非活动对象』进行回收
-简单说就是 它会把循环引用的内存空间,打上标记,然后回收掉
# 分代回收
把对象分为三代,一开始,对象在创建的时候,放在一代中,
如果在一次一代的垃圾回收检查中,该对象存活下来,就会被放到二代中,
放在二代中,说明该变量名被用到的概率,比放在一代中的变量名用到的概率高
同理在一次二代的垃圾检查中,该对象存活下来,就会被放到三代中,
检查第一代中的对象的时间间隔最短,优先回收,第二代中的对象检查的时间间隔长一点
第三代检查的时间间隔最长
----------------------------------------------------
.
.
.
解释为什么计算密集型用多进程,io密集型用多线程 ?
-----------------------------------------------------
1 解释为什么计算密集型用多进程,io密集型用多线程
-由于GIL锁的存在,即便是多核机器,同一时刻,也只能有一个线程在执行
-线程需要cpu去调度执行
-如果开了多线程,是计算密集型,计算是消耗cpu的,
假设是四核电脑,不能充分利用这四个核,只能有一个核在执行,开多线程没有用
-而如果计算密集型,开了多进程,GIL是在cpython解释器进程中的,
-在进程中开启线程执行计算,可以充分利用多核优势
-开了一个进程,就开了一个cpython解释器的进程,就会有一个gil锁
-由于GIL锁的存在,如果是计算密集型,开启多线程,不能利用多核优势,
-开启多进程,可以利用多核优势
-----------------------------------------------------
2 即便是io密集型,用多进程是不是会显著提高效率?
本身开启进程是非常消耗资源的,如果是io密集型,没有必要开多进程,并不会有显著的效率提升
io密集型推荐使用多线程,io不耗费cpu,开启多线程,在一个时间段内是有并发效果的
.
.
.
.
.
.
为什么有了gil锁还要互斥锁
gil:全局解释器锁,线程要执行,必须先获得到gil锁,才能执行,
保证了垃圾回收线程不会将其他线程正在使用的变量给回收掉
互斥锁:为了保证多线程并发操作数据(变量)的安全,而设置的锁,
保证在加锁和释放锁之间的代码,其他线程不能运行
gil本质也是一个大的互斥锁
--------------------------------------------------------
# 出现了数据错乱,出现了多条线程操作变量,出现的并发安全问题
a=0
线程1要计算: a += 1
1 线程1 拿到gil锁
2 读取a=0
3 假设时间片到了,释放gil锁,释放cpu
4 等待下次被调度执行
10 轮到它了,再获取gil锁
11 继续往下执行:计算 a+1
12 把结果赋值给a ,a=1 # 这地方就有问题了,按理应该是a=2 才是合理的,所以要有互斥锁
13 释放gil锁
线程2要计算: a += 1
5 线程2获得了gil锁
6 读取a=0
7 计算a+1
8 把结果赋值给a ,现在 a=1
9 释放gil锁
--------------------------------------------------
# 什么临界区?
会出现并发安全问题的这段代码称之为临界区 ,所以要在临界区加锁
# 加锁
6 读取a=0
7 计算a+1
8 把结果赋值给a ,a=1
# 释放锁
--------------------------------------------------
# 互斥锁保证数据安全
a=0
线程1要计算: a+=1
1 线程1 拿到gil锁
# 加互斥锁
2 读取a=0
3 假设时间片到了,释放gil,释放cpu
4 等待下次被调度执行
7 轮到它了,获取gil锁
8 继续往下执行:计算a+1
9 把结果赋值给a ,a=1
10 释放gil锁
线程2要计算: a += 1
5 线程2获得了gil锁
# 获取互斥锁,获取不到
6 释放gil锁
11 获得gil锁
#加锁
12 读取a=1
13 计算a+1
14 把结果赋值给a ,a=2 # 这样不同线程间的临界区代码,通过互斥锁,实现串行,保证数据不错乱
15 释放锁
16 释放gil锁
# gil锁并不锁住临界区,临界区需要我们自己 加互斥锁
.
.
.
.
.
进程,线程和协程 重要
进程:进程是资源分配的最小单位,一个应用程序运行,会至少开启一个进程
比如qq程序右键运行后,会以一个进程的形势运行在内存里面
一个qq可以有很多功能,原因是该进程里面有很多线程
线程:线程是cpu调度的最小单位,或者说是cpu执行的最小单位,一个进程下至少有一个线程
协程:单线程下实现并发效果,程序层面遇到io,控制任务的切换,从而不释放cpu
# 说白了进程就是线程的容器,线程是进程内部的执行流
# 进程之间是相互独立的,线程之间是共享资源的
# 操作系统只会为进程分配资源,不会为线程分配资源
# 注意开启子进程与开启子线程执行任务函数代码, 是一个异步操作!!!
# 主进程或主线程立马就往下执行了,是不会等子进程或子线程运行完了再往下执行代码的!!
----------------------------------------------------
代码如何实现
# 开启多进程两种方式
-1 写一个类,继承Process,重写类的run方法---》实例化得到对象,对象.start 开启了进程
-2 通过Process类实例化得到一个对象,传入任务 ,调用对象.start 开启了进程
你在哪里用过
# 同理开启线程两种方式
-1 写一个类,继承Thread,重写类的run方法---》实例化得到对象,对象.start 开启了进程
-2 通过Thread类实例化得到一个对象,传入任务 ,调用对象.start 开启了进程
你在哪里用过
------------------------------------
# 开启协程 重要!!!
早期之前:借助于第三方gevent模块,该模块是基于greelet写的
后来用 async 和 await 关键字,可以不借助于第三方模块,开启协程
import asyncio 导入内置模块
必须写在一个函数前, async def task()---->这个函数执行的结果是协程函数
await 只要是io操作的代码,前面必须加 await
---------------------------------------------------
# 在哪用过
我一般遇到计算密集型的操作,我会开多进程
一般遇到 io密集型的操作,我一般开多线程
爬别人数据,喜欢开多线程,因为爬虫 io居多
程序中,异步做一件事情,也可以开多线程
比如一个视图函数,要异步的把数据写的文件中
异步的发送钉钉通知
异步的发送邮件
但实际上,在项目中,不需要我们开启进程线程,可以借助于第三方的框架比如celery就可以做异步的操作
而celery的worker,就是进程线程架构
django框架,是支持并发,我们没有开启多进程和多线程,
但是符合uwsgi的web服务器在进入djagno框架之前,开启了进程和线程来执行视图函数
.
.
.
.
.
什么是鸭子类型
# 鸭子类型是 编程语言中动态语言中的一种概念
# 不需要显式的继承一个类
# 不去关心这个对象本质是什么,而是关心这个对象所具有的方法与属性。
l1 = [1, 2, 3]
print(len(l1))
class B:
# 我们需要B这个类和它产生的对象也像列表一样也有一个长度怎么办?添加个方法试试
def __len__(self):
return 10
def __iter__(self):
return iter([1, 2, 3])
b = B()
print(len(b)) # 可以计算长度 结果是10
# 可以正常for循环
for i in b:
print(i)
# 也就是说python解释器不会去检查发生多台的对象到底属于是什么类型
# 只会去检查有没有对应的方法,检查到了,就可以用
# 也就是说无论对象b到底是什么类型,只要我们给它添加想要的方法和属性
# 它就变成了什么
python 是鸭子类型的语言 java不是鸭子类型的语言
.
.
.
接口是什么意思
接口就是在一个程序的类中实现的某一个功能,而这个功能有可以被其他的成员或者是对象所引用 。
接口是规范或约束子类的行为,让这些子类属于同一类。可以简单的理解为父类
.
.
.
.
.
.
.
.
.
.
什么是http协议 与 https协议 有什么区别
HTTP是应用层协议
超文本传输协议( Hyper Text Transfer Protocol,HTTP )是一个简单的请求-响应协议,
它通常运行在TCP之上。
-------------------------------------------------
超文本传输安全协议 ( Hypertext Transfer Protocol Secure )
HTTPS 主要由两部分组成:HTTP + SSL / TLS
SSL Secure Socket Layer 安全套接字层协议 用于为数据通信提供安全支持
TLS Transport Layer Security 传输层安全协议
用于在两个通信应用程序之间提供保密性和数据完整性
--------------------------------------------------
# http和https的区别
https=http+ssl/tls
监听端口不一样, http是8080 https是443
http明文传输 https密文传输
.
.
.
.
.
什么是WebSocket协议
----------------------------------------------------------------
WebSocket 是独立的、创建在 TCP 上的协议。
WebSocket 是一种在单个TCP连接上进行全双工通信的协议
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。
在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性
的连接,并进行双向数据传输。
----------------------------------------------------------------
# WebSocket出现的原因
很多网站为了实现推送技术,所用的技术都是轮询。轮询是在特定的时间间隔(如每1秒),
由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。
这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求
在这种情况下,HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
也是要先客户端发送请求,连接服务端,服务端响应连接请求后,不断开连接,这样
服务端就可以一直朝客户端发消息了!!!
----------------------------------------------------------------
.
.
.
并发编程里面的一些名词解释
semaphore信号量作用: 一次性产生多把互斥锁
Event事件作用: 实现了一个线程控制另一个线程代码的执行
当子进程或者子线程中有event.wait()代码,那么一定要等某一个地方执行了event.set()后,
event.wait()下面的代码才能继续正常运行!!!否则就卡在event.wait()这不运行了!!!
进程池的作用:提前创建好固定数量的进程供后续程序的调用,程序的数量超出,则需要等待
线程池的作用:提前创建好固定数量的线程供后续程序的调用,程序的数量超出,则需要等待
.
.
.
什么是猴子补丁,有什么用途
猴子补丁:在程序运行过程中,动态替换程序中代码的一种技术
-------------------------------------------
# 用途:
-1 比如咱们json模块,用内置的json,效率低,有一个第三方的ujson模块
-不改变原来程序代码,把程序中所有json都替换成ujosn
-在程序运行的入口处:import ujson as json
-------------------------------------------
-2 django中 pymysql的替换
# 本来python2中 django中用来连接mysql的模块用的是mysqlDB conn也是用mysqlDB类生成的对象
# 但是python3中 mysqlDB用不了
import pymsql
pymysql.install_as_mysqlDB() # 这句话执行完了后,原来的mysqlDB类生成的对象conn 替换成了
# pymsql类生成的对象conn了
-------------------------------------------
-3
from gevent import monkey
monkey.pach_all()
gevent---》猴子补丁---》monkey.pach_all()---》动态的替换内部的会阻塞的代码,比如:
-time
-socket
-把所有内置的会释放gil锁的模块,动态替换成gevent自己写的,不会释放gil锁的模块
-同步代码:io操作,会阻塞,会释放gil锁,这条线程就会释放cpu
-所以写了一个monkey.pach_all()方法
-动态的把所有同步的代码,都替换成异步代码,动态的替换所有会阻塞的代码
-异步代码,遇到io,不释放gil锁
.
.
.
.
.
.
.
.
描述 从浏览器输入一个地址,到看到页面信息,经历的过程
1 浏览器输入域名后,DNS解析域名,获取对应的IP地址与端口
(先从浏览器缓存、再从本机缓存、再从本机host文件、再从DNS服务器找 一级一级往上找)
如果都解析不到,页面就会报错。解析完后,向解析出的IP地址与端口,建立TCP连接,进行3次握手
2 浏览器向某个地址发送http请求
3 如果后端服务是使用nginx转发,nginx把http请求转发给web框架(django,flask等),进入框架
4 后端服务器以http响应的形式,返回给客户端浏览器
5 客户端浏览器把http响应体的内容展示在浏览器上,http响应的状态码与响应头内容不展示在浏览器页面上
6 四次挥手断开tcp连接---》http协议1.1版本有个keep-alive可以保持长连接,所以不一定会断开tcp连接
# 参考博客 https://blog.csdn.net/m0_52165864/article/details/126313277
.
.
.
.
什么叫 sql的迪卡尔积 ?
select * from emp2,dep2; 会将两张表中所有的数据对应一遍
就是无脑用一个表的所有行数据,分别与另一个表里面的每一行数据进行拼接,而形成的数据
假设emp2表里面有10行数据,dep2表里面有5行数据,这样连表查出来的表里面就有50条数据
可以简单地理解为一张表里面的每条数据,与另一张表里面的每条数据间的任意组合。
当两张表的数据很多的时候,所以这样连表操作,极其浪费资源!!!
所以用笛卡尔积的方式连表并不合理!!!
.
.
.
.
sql的 左连接,右连接,内连接,全连接 是什么?
有时候我们需要的数据不在同一张表中,就需要连表操作,但是用笛卡尔积的方式连表操作,及其浪费资源
所以有了 左连接,右连接,内连接,全连接 的连表操作了
left join 左连接:以左表为基准 展示左表所有的数据 如果右表没有对应项,则用NULL填充
right join 右连接:以右表为基准 展示右表所有的数据 如果左表没有对应项,则用NULL填充
inner join 内连接:以两张表共有的数据连表,左表与右表独有的数据都不要
union 全连接:以左右表为基准 展示所有数据 各自表没有的对应项,全部NULL填充
-----------------------------------
select * from emp inner join dep on emp.dep_id=dep.id;
emp inner join dep 将两个表连接在一起,on后面接的连接的依据!!!
-----------------------------------
select * from emp left join dep on emp.dep_id=dep.id;
以左表为基准 展示左表所有的数据,右表如果没有对应项则用NULL填充!!!
-----------------------------------
select * from emp left join dep on emp.dep_id=dep.id
union
select * from emp right join dep on emp.dep_id=dep.id;
左右表都为基准,展示所有的数据,各自没有的全部NULL填充!!!
# 实际上就是将左连接查出来的数据,与右连接查出来的数据,放到一起,做一个去重操作!!!
-----------------------------------
内连接以两张表共有的数据连表
.
左连接 以左表为基准 展示左表所有的数据 右表如果没有对应项则用NULL填充!!!
.
全连接
.
.
.
全连接的 union和union all的区别 ?
union就是全连接
主要区别在于,union会自动去除重复行,而union all不会 !!!
因此,如果你想要完全保留所有的行,包括重复的行,可以使用 union all
# 数据必须所有字段都一样才能算重复
select 出来结果,union,union all都是对结果进行合并,求并集
union 会去除重复的数据
union all 不会去除重复的数据
# 比如:
select name,id form user;
id name
1 lqz
2 zs
select name,id form book;
id name
1 lqz
2 西游记
3 金瓶没
# 用union all 不会去重
select name,id form user union all select name,id form book;
id name
1 lqz
1 lqz
2 zs
2 西游记
3 金瓶没
-------------------------------------------
# 用union会去重
select name,id form user union select name,id form book;
id name
1 lqz
2 zs
2 西游记
3 金瓶没
.
.
.
什么是socket
Socket是 HTTP协议所在的应用层 与 TCP/UDP协议所在的传输层 中间的一个 软件抽象层!!!
它把复杂的TCP/UDP协议的接口,隐藏在Socket简单的接口后面,这样用户只需要调用socket简单的接口
就可以调用 复杂的TCP/UDP协议的接口了
.
.
.
面向对象的三大特性,及使用场景
继承:表示类与类之间资源的从属关系 继承的本质为了节省代码
封装: 就是将数据和功能'封装'起来
多态: 一类事物的多种形态
继承的使用场景:
定义的一个类,该类里面需要调用使用父类里面的方法,
或者在父类方法的基础上再加一些新的功能,就要用到继承
封装的使用场景:
一般是当多个类里面有相同的方法或属性时,可以把这些共有方法与属性抽取出来,
封装到一个新的类里面,这样其他的类只需要继承该类就可以了
多态的使用场景:
比如 当多个子类继承同一个父类,并重写了该父类的方法,
这样多个子类里面就具有相同的方法,但是每个子类里面该方法的执行代码却不一样
.
.
.
.
.
.
什么叫双写一致性,及如何保证? 重要!!!
解释:指的是数据库写操作 和 缓存更新操作 的一致性问题
# 从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案!!!
如果mysql中数据变了,缓存不会自动变化,就会出现数据不一致的问题
-----------------------------------------------------
# 不依赖于给缓存设置过期时间,也保证双写一致性的三种策略 :
1 修改数据库,再删除缓存 # 下次请求来了,会再走一次数据库
2 修改数据库,再更新缓存 # 下次请求来了,会直接走redis 不太推荐!!
3 定时更新缓存 ----------实时性差
--------------------------------------------------
# 修改数据库,再删除缓存方案的优缺点:
优点:理论上来讲,是最好的方案
缺点:如果删缓存失败,也会出现数据不一致的情况
怎么解决:提供一个重试机制即可,比如起一个 while True 循环 加个计数 删除失败就继续再删
并计数加1,计数设置个上限, 如果连续删除次数到了上限还没删除成功,通知后端并结束循环
这时候就可能是redis出问题了,双写一致性就已经不是首要问题了!!!
--------------------------------------------------
# 修改数据库,再更新缓存方案的优缺点:
优点:比删除缓存的方案 当请求再来时,少走一次数据库
缺点:并发情况下可能会出现缓存脏数据,线程不太安全 比如
线程A更新了数据库
线程B更新了数据库
因为网络等原因,导致线程B先更新了缓存
线程A更新了缓存 # 这就导致了缓存出现脏数据了!!!可以加互斥锁解决但是影响性能了 需要权衡一下
还有一种情况就是: 假如业务操作是写多读少的场景,缓存就被频繁的更新,浪费性能 。
--------------------------------------------
# 定时更新缓存方案的缺点:实时性差
.
.
.
.
.
.
tcp 三次握手和四次挥手 重要!!!
# 补充:
TCP是可靠连接,使用三次握手,四次挥手保证了可靠连接,数据不会丢失
SYN:SYN=1 表示要建立连接
FIN:表示断开连接
ACK:ACK=1 表示我收到了
seq:随机数,建立连接无论客户端还是服务端要建立连接就要要携带
ack:回应请求就要加1返回
---------------------------------------------------
# 三次握手:建立链接
第一次握手:
客户端发送一个建立通信通道的链接请求
客户端发送:喂(SYN=1),我是lqz(seq=随机数),并处于SYN_SEND状态
服务端:没收到处于: listen 状态, 收到了处于: SYN_RCVD状态
-------------------------
第二次握手:
服务端确认了建立通信通道的链接请求,
服务端发送:收到(ACK=1),lqz啊(ack=随机数+1),客户端到服务端的通信通道建立
并接着发送一个 喂(SYN=1),我是刘亦菲(seq=另一个随机数)
建立服务端到客户端的通信通道的链接请求 处于:SYN_RCVD状态
客户端:没收到服务端返回的第二次:SYN_SEND状态,一旦收到就是established
-------------------------
第三次握手:
客户端确认了建立通信通道的链接请求
客户端发送:收到(ACK=1),刘亦菲你好(ack=另一个随机数+1)
客户端处于 连接建好的状态 established
服务端:收到后,处于established
三次握手后,就建立了双向通道了!!!
-------------------------------------------------
# 大白话:三次握手 每一次发送 带的什么东西 一定要背出来
第一次:客户端向服务端发送建立连接请求,携带一个随机数(SYN=1,seq=随机数)
第二次:服务端回应客户端的建立连接请求(表示收到ACK=1, 并回应ack=随机数+1),
服务端接着发送建立连接请求(建立连接SYN=1,加seq=另一个随机数)
第三次:客户端回应服务端的建立连接请求(ACK=1,ack=另一个随机数+1)
# 洪水攻击:
同一时间有大量的客户端请求建立连接 会导致服务端一直处于SYN_RCVD状态,服务端接收到了大量的syn请求,处于rcvd状态
-------------------------------------------------
图解
.
.
.
.
# 四次挥手:断链接
第一次:客户端向服务端发起断开连接的请求(FIN=随机数) ,客户端不再发送数据了
第二次:服务端收到后,回复这个请求(ACK=1,ack=随机数+1)
第三次:服务端向客户端发起断开连接的请求(FIN=另一个随机数),服务端不再发送数据了
第四次:客户端收到后,回复这个请求(ACK=1,ack=另一个随机数+1)
--------------------------------------------------
# 四次不能合并为三次 ?
因为服务端接收到客户端的fin后,可能还有数据要发送给客户端!!!
客户端发来的FIN报文,仅仅表示自己不再发送数据了,但还是可以接受数据的。
所以服务端先发送ACK回应客户端,当服务端数据发送完了,或者不想发了,
再发送FIN报文给客户端表示关闭链接,因此,关闭链接时ACK和FIN是分开发送的。
--------------------------------------------------
.
.
.
.
.
osi七层协议,哪七层,每层有哪些
osi七层: 应用层 表示层 会话层 传输层 网络层 数据链路层 物理连接层
或五层结构: 应用层(三个合成一个) 传输层 网络层 数据链路层 物理连接层
---------------------------------------------
-应用层 表示层 会话层(应用层)
-应用层协议:http,ftp,https,dns
-表示层:https=http+ssl 的加密
-会话层:负责建立、管理和终止表示层实体之间的会话连接
-传输层:
-tcp协议
-udp协议
-端口:端口协议
-网络层 -ip地址协议
-数据链路层:
-mac地址:以太网协议
-数据帧:电信号的分组方式
-物理层:
-物理介质,网线
-----------------------------------------
.
.
.
tcp和udp的区别? udp用在哪里了?
区别:
TCP协议称之为流式协议、面向连接的可靠协议(数据不容易丢失)
UDP协议称之为数据报协议、面向无连接的不可靠协议
# 用在哪里了?
-比如:
udp:一些聊天工具,dns协议用的udp协议
tcp:浏览器发送http请求 mysql或redis客户端与服务端通信
TCP我们可以打比方为打电话 你一句我一句
UDP我们可以看成是发短信 只要发了就行 不管对方看不看
.
.
.
.
.
.
.
.
web服务器到底是什么?
客户端(浏览器,app) 跟 服务端(web框架)之间的东西 ,又叫服务器中间件
----------------------------------------------
# nginx apache 是一类东西,就是做请求转发
# uWSGI,wsgiref 是一类东西 针对于python的web框架
# tomcat, 针对java的web框架
----------------------------------------------
浏览器--》--Nginx--》--web服务器--》--web框架
----------------------------------------------
.
.
.
WSGI uWSGI uWSGI, cgi, fastcgi 分别是什么?
# 大写的WSGI与小写wsgi是一个东西
# WSGI:(Python Web Server Gateway Interface)
是为Python定义的 Web服务器 和 Web应用程序或框架之间的 一种通用的接口协议
# 一句话总结: 为Python定义的 web服务器和web框架之间的接口标准
# 符合WSGI协议的web服务器有哪些:uWSGI wsgiref werkzeug等
----------------------------------------------
----------------------------------------------
# uWSGI:
符合wsgi协议的web服务器,用c写的,性能比较高,咱们通常用来部署django,flask
# 一句话总结:一个符合WSGI协议的 web服务器,
# 处理发来的请求及返回响应。
----------------------------------------------
----------------------------------------------
# 小写的uwsgi
# uwsgi:
是uWSGI服务器 自己的标准
用于定义传输信息的类型,是用于前端服务器与 uwsgi 的通信规范
# 1、一句话总结: uWSGI自有的一个协议
----------------------------------------------
----------------------------------------------
# cgi:(Common Gateway Interface/CGI)通用网关接口协议
是一个用来 规定了web服务器(nginx,apache)到 请求处理程序(django,flask,
等web框架)之间传输数据的标准
所有bs架构软件都是遵循CGI协议的
一句话总结:一个标准,定义了客户端浏览器与服务器之间如何传输数据
----------------------------------------------
----------------------------------------------
# FastCGI:(Fast Common Gateway Interface/FastCGI) 快速通用网关接口协议
是一种让交互程序与Web服务器通信的协议。 FastCGI 是 CGI的增强版本
常见的fastcgi服务器:Apache,Nginx,Microsoft IIS
# 一句话总结: CGI的增强版 从而使服务器可以同时处理更多的网页请求
----------------------------------------------
----------------------------------------------
.
.
.
.
.
.
.
什么叫上下文管理器? 如何自定制上下文管理器?
# 上下文管理器:
一个类,实现了__enter__和__exit__方法,这个类的对象就是上下文管理器对象
就可以使用with关键字,使用with关键字后,
执行with下面的代码前,先执行上下文管理器里面的双下__enter__方法
with下面的代码执行完后,再执行上下文管理器里面的双下__exit__方法
------------------------------------------------------
# 使用场景:
上下文管理器 适用于那些进入和退出之后自动执行一些代码的场景,
比如文件、网络连接、数据库连接或使用锁,s使用事务的编码场景等
或者简单地说:一个东西的打开与关闭, 就可以使用到上下文管理器
------------------------------------------------------
# 如何使用
class A:
def __enter__(self):
print('进入with语句块时执行此方法,如果有返回值会赋值给as声明的变量')
return 'xxx'
def __exit__(self, exc_type, exc_val, exc_tb):
print('退出with代码块时,执行此方法)
print('1', exc_type)
print('2', exc_val)
print('3', exc_tb)
with A() as f: # f 就是双下enter返回的值
print(f) # xxx
print(type(f) ) # <class 'str'>
print('哈哈哈')
print('上下文管理器结束了') # 执行该代码前,会执行双下exit方法
进入with语句块时执行此方法,如果有返回值会赋值给as声明的变量
xxx
<class 'str'>
哈哈哈
退出with代码块时,执行此方法
1 None
2 None
3 None
上下文管理器结束了
-----------------------------------------------------
.
.
.
.
.
Python是值传递还是引用传递
# 严格意义上来说,python既不是值传递,也不是引用传递
可变类型数据当参数传到函数中,在函数中修改会影响原来的变量,不是重新赋值
不可变类型数据当参数传到函数中,在函数中修改不会影响原来的变量
# 因为python一切皆对象,对象都是指向内存地址,都是引用
-----------------------------------------------
# 什么是值类型,什么是引用类型
值类型的数据 是一个变量=具体的值(一块内存空间放着这个变量的值)
引用类型的数据 是一个变量=内存地址(内存地址再绑定值)
值类型的数据是变量名直接绑定值
引用类型的数据是变量名先绑定内存地址,内存地址再绑定最终的值
-----------------------------------------------
# 什么是值传递 什么是引用传递
如果是 引用类型的数据,传到函数里面,数据修改就会影响原来的
如果是 值类型的数据,传到函数里面,数据修改不会影响原来的
-----------------------------------------------
.
.
.
python一切皆对象怎么理解 ?
python一切皆对象,对象都是指向内存地址,都是引用
所以所有东西都可以赋值给一个变量名
# 所以python可以:
直接把一个函数/对象 作为另一个函数的参数,
用一些基本类型的数据,直接去点一些方法
.
.
.
django 的命令是怎么制作的,有空再学学
.
.
.
django 中 什么叫信号
当发生一些动作的时候,发出信号,然后监听了这个信号的函数就会执行。
.
.
.
Dockerfile用过吗?常永命令有哪些
Dockerfile是由一系列命令和参数构成的脚本文件 就是一个文件,用来构建镜像的
FROM 基础镜像 # 基于哪个基础镜像来 构建新镜像
MAINTAINER lqz # 声明 新镜像的创建者
ENV key value # 设置环境变量 (可以写多条)
RUN command # 在容器执行的命令
.
.
.
.
.
.
.
.
.
.
.
装饰器
# 装饰器:本质是一个闭包函数
作用是在不改变 被装饰对象源代码和调用方式的基础上,为它加入新功能
闭包函数:定义在函数体内部,但是用到外部名称空间的变量的函数
使用场景:
一些视图函数,需要登录用户才能访问,就可以加登录装饰器
django中局部去除csrf认证 view = csrf_exempt(view)
flask的视图函数的路由注册就是基于装饰器 @app.route('/路径')
通过装饰器,运行add_url_rule函数,注册路由
.
.
.
.
django的信号用过吗?如何用,干过什么
------------------------------------------------
django提供的一种通知机制,他是一种设计模式,观察者模式,
在发生某种变化的时候,通知某个函数执行
内置信号:如果是内置信号用起来简单,
比如:pre_save,pre_delete,数据库数据保存删除前 就会触发信号
只需要写个函数,跟内置信号绑定,
当信号被触发,函数就会执行
------------------------------------------------
在django中 有两种绑定信号的方式
1 @receiver(信号名) 装饰对应的函数
2 信号名.connect(对应的函数名)
自定义信号:就比内置信号多了两步:1 定义信号 2 触发信号 (信号.send)
# 信号的使用场景:
比如自定义信号的使用,可以在用户异地登录的时候,触发自定义信号,执行信号绑定的,
发送短信提醒的函数
.
.
.
.
.
.
.
django 的设计模式
Django其实也是一个MTV 的设计模式。MTV是Model、Template、View三个单词的简写,分别代表模型、模版、视图。
.
.
.
.
.
.
.
restful规范
10个 说几个出来就行了
多版本共存,路径中带版本信息
在请求地址中带过滤条件
返回数据中带错误信息
响应中状态码
数据即是资源,均使用名词,尽量不出现动词
.
.
.
.
.
.
并发 并行
# 并发 同一时间段内,执行多个任务的能力
# 并行 同一时刻,执行多个任务的能力
# 并行必须是多cpu才能支持
.
.
.
.
阻塞 非阻塞
# 程序运行的状态 阻塞态 就绪态 执行态
# 程序执行的角度
# 阻塞 程序在等待某个操作完成期间,自身cpu被释放了,则称该程序在该操作上是阻塞的
比如程序在等待读写操作完成期间,或者sleep期间 ,自身不能干其他事情,因为cpu已经被调走了
# 非阻塞 程序在等待某个操作过程中,自身cpu不释放,则称该程序在该操作上是非阻塞的
只要程序在等待某个操作的过程中,cpu不放就是非阻塞!!就可以继续运行干别的事情
比如协程 在等待sleep操作的过程中,程序层面自动切换,运行其他代码,cpu就不释放了!
# https://zhuanlan.zhihu.com/p/621717027
.
.
.
.
同步与异步的区别:
----------------------------------------------------
# 调用程序的角度
# 同步:只有执行完前一个任务,才会执行下一个任务。同步意味着有序
# 异步:当一个任务已经执行,无需等待该任务执行完成,就可以切换到另外一个任务上。异步意味着无序
.
.
.
什么是IPC ? 进程间如何通信 ?
IPC 进程间通信
# 1 什么是IPC,如何进行进程间通信
-IPC:Inter-Process Communication,进程间通信
-两种情况:
-同一台机器上的两个进程通信
-不同机器上的两个进程进行通信
------------------------------------------
# 如何通信:
-------------
-pyton中多进程与多线程模块里的queue队列 可以做进程间通信 先进先出
q = Queue(10) # 生成队列并规定能存放数据的量
q.put(数据) # 往队列里放数据
q.get() # 从队列里面取数据
# 该队列只能满足简单的进程间通信用
--------------
-消息队列: redis就可以做消息队列,rabbitmq,kafka
--------------
-socket套接字: 展现形式比如:服务和服务之间通过接口调用
.
.
.
什么叫正向代理 反向代理
# 2 正向代理,反向代理
-正向代理:代理的是客户端 比如 VPN 爬虫代理池 # 服务器看不到客户端
-反向代理:代理的是服务端 比如 nginx # 客户端看不到服务器
.
.
.
.
.
什么叫粘包问题 如何解决
-因为TCP是流式协议,tcp客户端发送的多个数据包就会像水流一样流向TCP服务端,
多个数据包就会'粘'在一起,区分不开是几个数据包,造成了粘包现象
# 解决办法:
-1 每个包设置结束标志符 http协议采用这种方法 /r/n/r/n
-2 每个包设置固定大小的头,头可以解析出包的大小
# 机器与机器间通信只需要通过ip地址就行了
.
.
.
.
.
.
http协议详情,http协议版本,http一些请求头
# 特点:
1 基于请求响应--》服务端不能主动给客户端推送消息---》websocket协议
2 无状态无连接---》不能做会话保持---》才出现了cookie,session,token
3 基于tcp之上的应用层协议
---------------------------------------------
# 详情:
-请求协议:
请求首行:请求方式(get,post,delete),请求地址,请求http协议版本号/r/n
请求头:key:value (cookie,useragent,referer,x-forword-for)
请求体:编码方式
-响应协议:
响应首行:响应状态码(1xx,2xx),响应单词描述
响应头:key:value 跨域问题的响应头
响应体:html格式:浏览器中看到的 json格式给客户端使用
----------------------------------------------
-协议版本
-0.9:
HTTP协议的最初版本,功能简陋,仅支持请求方式GET,并且仅能请求访问HTML格式的资源
------------------------------------------
-1.0:
但是1.0版本的工作方式是每次TCP连接只能发送一个请求(默认短链接),
当服务器响应后就会关闭这次连接,下一个请求需要再次建立TCP连接,
就是不支持keep-alive
-------------------------------------------
-1.1:
引入了持久连接(persistent connection),即TCP连接默认不关闭,
可以被多个请求复用,不用声明Connection: keep-alive。客户端和服务器
发现对方一段时间没有活动,就可以主动关闭连接。
-------------------------------------------
-2.0:
多路复用:对于 HTTP/1.x,即使开启了长连接,请求的发送也是串行发送的,
在带宽足够的情况下,对带宽的利用率不够,HTTP/2.0 采用了多路复用的方式,
可以并行发送多个请求,提高对带宽的利用率!!!
--------------------------------------------
-3.0:
HTTP3.0又称为HTTP Over QUIC,其弃用TCP协议,
改为使用基于UDP协议的QUIC协议来实现
.
.
.
.
GET请求和POST请求的区别
post更安全(不会作为url的一部分)
post发送的数据更大(get有url长度限制)
post能发送更多的数据类型(get只能发送ASCII字符)
post比get慢
post用于修改和写入数据,get一般用于搜索排序和筛选之类的操作
.
.
.
.
如何实现服务器给客户端发送消息,websocket是什么?用过吗
-websocket:一种通信协议,区别于http协议,可在单个TCP连接上进行全双工通信。允许服务端主动向客户端推送数据。浏览器和服务器只需要完成一次tcp连接,两者之间就可以建立持久性的连接,并进行双向数据传输
-没有跨域问题
-应用场景:
-只要涉及到服务端主动给客户端发送消息的场景,都可以用
-实时聊天:我项目没写
-服务端发生变化,主动向客户端推送一个通知
-监控服务端的内存使用情况
-djanog中使用channles模块实现
-# django后端如何实现websocket参考博客 https://zhuanlan.zhihu.com/p/371500343
.
.
.
.
.
悲观锁和乐观锁,如何实现
-无论是悲观锁还是乐观锁,都是人们定义出来的概念,仅仅是一种思想,与语言无关
---------------------------------------
# 并发控制:
并发控制:当程序中出现并发问题时,就需要保证在并发情况下数据的准确性,以此确保当前用户和其他用户一起操作时,所得到的结果和他单独操作时的结果是一样的,这种手段就叫做并发控制
没有做好并发控制,就可能导致脏读、幻读和不可重复读等问题
-----------------------------------------
# -悲观锁:
当要对一条数据进行修改的时候,为了避免同时被其他人修改,
在修改数据之前先锁定防止并发,再修改的方式 被称之为悲观并发控制 又名"悲观锁"
之所以叫做悲观锁,是因为这是一种对数据的修改持有悲观态度的并发控制方式。
当对这一行的数据加了悲观锁后,其他线程对这行数据,连读的权利都没有了。
# -悲观锁实现方式:
1 传统的关系型数据库(如mysql)使用这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁
2 编程语言中的线程锁,比如python的互斥锁
3 分布式锁:redis实现的分布式锁等
--------------------------------------------
.
.
.
乐观锁
# -乐观锁:
通过程序实现(没有真正的一把锁),不会产生死锁现象。
总是假设读数据和要改数据的期间,别人不会修改,所以不会上锁,
只在更新的时候会判断一下,期间数据有没有发生变化,如果有,就不做修改!!!
----------------------------------------------
# 乐观锁实现方案:
1 版本号控制:在数据表中增加一个版本号字段,读操作的时候先获取版本号
每次更新数据时,再获取一下版本号与之前获取的版本号,如果一致,说明期间数据没改
更新数据,并将版本号加 1 否则更新失败
2 时间戳方式:在数据表中增加一个时间戳字段,
每次更新数据时,查看时间戳与一开始查看的时间戳,如果一致,说明期间数据没改
更新数据,并更新时间戳 否则更新失败
----------------------------------------------
# 悲观锁乐观锁使用场景
并发量:如果并发量不大,可以使用悲观锁解决并发问题;但如果系统的并发非常大的话,悲观锁定会带来非常大的性能问题, 建议乐观锁
响应速度:如果需要非常高的响应速度,建议采用乐观锁方案,成功就执行,不成功就失败,不需要等待其他并发去释放锁。乐观锁并未真正加锁,效率高
冲突频率:如果冲突频率非常高,建议采用悲观锁,保证成功率。冲突频率大,选择乐观锁会需要多次重试才能成功,代价比较大
重试代价:如果重试代价大,建议采用悲观锁。悲观锁依赖数据库锁,效率低。更新失败的概率比较低
读多写少: 乐观锁适用于读多写少的应用场景,这样可以提高并发粒度
------------------------------------------------
.
.
.
.
.
django中如何开启事务
#django中使用:下单,秒杀场景
# 先建个表
from django.db import models
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.IntegerField()
count = models.IntegerField()
class Order(models.Model):
order_id = models.CharField(max_length=64)
order_name = models.CharField(max_length=32)
-------------------------------
# django中如何开启事务
-全局开启:每个http请求都在一个事务中
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'lqz',
'HOST': '127.0.0.1',
'PORT': '3306',
'USER': 'lqz',
'PASSWORD': 'lqz123',
#全局开启事务,绑定的是http请求响应整个过程
'ATOMIC_REQUESTS': True,
}
}
------------------------
-每个视图函数开启
from django.db import transaction
@transaction.atomic
def seckill(request):
pass
------------------------
-局部开启 在某一段代码上开启事务
from django.db import transaction
def seckill(request):
with transaction.atomic():
print('hahaha')
# 事务里面的所有代码都在一个事务中
# 一旦在with下面写同级的代码,事务就提交了,事务里面的代码才会真正执行
# 用了with上下文就不需要手动提交事务了
return HttpResponse('秒杀成功')
------------------------
# 回滚点Savepoint
设置回滚点:sid = transaction.savepoint()
提交回滚点:transaction.savepoint_commit(sid)
回滚到回滚点:transaction.savepoint_rollback(sid)
事务提交后,自动运行回调函数 transaction.on_commit(send_email)
-------------------------
# 事务
提交整个事务 transaction.commit()
整个事务回滚 transaction.rollback()
-------------------------
## 回滚点的使用 代码示范
def seckill(request):
# 局部开启事务
with transaction.atomic():
sid = transaction.savepoint() # 设置回滚点
try:
book = Book.objects.get(pk=1)
book.name = 'xxx'
book.save() # 没有真正提交
Order.objects.get(pk=1)
transaction.savepoint_commit(sid)
except Exception as e:
# 如发生异常,回滚到指定地方
transaction.savepoint_rollback(sid)
print('出异常了,回滚')
return HttpResponse('秒杀成功')
-------------------------
# 提交事务后执行的函数
def send_email():
print('发送邮件给卖家了')
def seckill(request):
with transaction.atomic():
book = Book.objects.get(pk=1)
book.count = book.count - 1
book.save()
transaction.on_commit(send_email)
return HttpResponse('秒杀成功')
.
.
.
.
.
.
django中如何使用悲观锁与悲观锁
## 使用mysql的行锁 做悲观锁
# Django中使用悲观锁锁定一个对象,需要使用select_for_update()方法。
# 它本质是一个行级锁,能锁定所有匹配的行,如果查询所有,可以锁住整张表,直到事务结束
import datetime
import time
import random
@transaction.atomic
def seckill(request):
sid = transaction.savepoint() # 设置回滚点
book = Book.objects.select_for_update().filter(pk=1).first()
# 锁住查询到的book对象,其他人连该对象读都读不了,直到这个视图函数结束,锁才释放
if book.count > 0:
print('库存可以,下单')
Order.objects.create(order_id=str(datetime.datetime.now()), order_name='测试订单')
# 库存-1,扣减的时候,判断库存是不是上面查出来的库存,如果不是,就回滚
time.sleep(2) # 模拟延迟
book.count=book.count-1
book.save()
transaction.savepoint_commit(sid)
return HttpResponse('秒杀成功')
else:
transaction.savepoint_rollback(sid)
return HttpResponse('库存不足,秒杀失败')
--------------------------------------------
--------------------------------------------
# django中使用乐观锁 在改数据前比较一下与上面读的数据一不一样,一样才改
@transaction.atomic
def seckill(request):
# 乐观锁可能会失败,我们一直尝试秒杀,直到秒成功或库存不足
while True:
sid = transaction.savepoint()
book = Book.objects.filter(pk=1).first()
count = book.count # 先查一下库存
print('现在的库存为:%s' % count)
if book.count > 0:
print('库存可以,下单')
Order.objects.create(order_id=str(datetime.datetime.now()), order_name='测试订单')
# 库存-1,扣减的时候,判断库存是不是上面查出来的库存,如果不是,就回滚
time.sleep(2) # 模拟延迟
res = Book.objects.filter(pk=1, count=count).update(count=count - 1)
print(res)
if res >= 1: # 表示修改成功
transaction.savepoint_commit(sid)
return HttpResponse('秒杀成功')
else: # 修改不成功,回滚
transaction.savepoint_rollback(sid)
print('被别人扣减了,继续秒杀')
continue # 继续秒杀
else:
transaction.savepoint_rollback(sid) # 库存不足也回滚
return HttpResponse('库存不足,秒杀失败')
.
.
.
.
.
.
docker相关知识
# docker的出现解决了什么?
解决了应用程序本地运行环境与生产运行环境不一致的问题
让快速扩展、弹性伸缩变得简单
# 详细解释为什么解决了不一致问题:
当我们用Dockerfile文件构建出一个python解释器 与 所有依赖的模块如django与uwsgi等 的镜像时
并且Dockerfile有一个cmd命令 启动项目的命令
镜像推到远程仓库,就可以在任意电脑上,先把项目文件拉下来,再把镜像拉下来,所有的依赖就有了,
镜像运行成容器,项目就直接运行起来了!!!
--------------------------------------------
# Docker的网络模式有哪些?
HOST模式 、Container模式 、None模式 、 bridge模式
--------------------------------------------
systemctl start docker 启动docker
systemctl stop docker 停止docker
--------------------------------------------
docker info 查看docker概要信息
docker pull 镜像名称:标签 拉取镜像 不指定标签,会下载最新版本
docker images 查看本地镜像
docker rmi 镜像ID 删除镜像
---------------------------------------------
docker ps 查看正在运行的容器
docker ps -a 查看所有容器
docker run -it --name=容器名 镜像名:标签 # 将镜像运行成指定名称的容器并进入
docker start mycentos # 启动停止的容器
---------------------------------------------
docker exec 容器id 命令 # 容器执行命令
docker exec -it 容器id /bin/bash # 外部进入到容器内部
docker run -id --name=lqz -v /home/sss:/sss centos:7 # -v做目录映射
-p 8888:3306 # 做目录映射
docker rm 容器id # 删除对应id的已停止的容器
docker rm `docker ps -a -q` # 删除所有已停止的容器
---------------------------------------------
docker commit 容器名 镜像名 # 把容器做成镜像
docker save -o 镜像名.tar 镜像名 # 镜像文件打包成压缩包
docker load -i 压缩包 # 压缩包解压成镜像
---------------------------------------------
# Dockerfile构建镜像
先编写Dockerfile文件
FROM 基础镜像
ENV key value # 设置环境变量
ADD 宿主机文件目录 容器目录 # 将宿主机的文件复制到容器内,压缩文件会后自动解压
CMD:启动容器时需要执行的命令
WORKDIR 目录名 # 设置工作目录
# 基于dockerfile构建镜像 要在Dockerfile文件所在的目录下 执行该命令
docker build -t='镜像名' .
# docker-compose 单机容器编排
docker-compose up # 启动管理的容器
---------------------------------------------
.
.
.
多路复用
.
.
.
.
ssh
.
.
.
.
.
celery相关知识
# 如何使用见文章 https://www.cnblogs.com/tengyifan888/articles/17344382.html
# celery架构:
任务中间件 broker 需要借助于redis 或 rabbitmq
任务执行单元 worker
结果存储库 backend 需要借助于redis 或 mysql
# 主要的使用场景
执行耗时任务用异步
执行一些循环定时任务或者延迟任务
.
.
.
.
.
.
.
.
shell 脚本
.
.
.
.
.
.
.
.
.
内网穿透
你的电脑没有公网ip,所以你电脑上起的服务项目,别人访问不到,
需要有一个具有公网ip的服务器 来监听你的电脑的端口
这样别人访问这个公网ip的的服务器,服务器转发请求到你的电脑上
就实现内网穿透了
.
.
.
匿名函数
# 没有名字的函数
语法结构 lambda 形参:返回值
.
.
.
表锁情况解决
生成器迭代器
垃圾回收
get请求celery异步下载图片流程
链表查询业务场景
具体项目业务流程
.
.
.
http常用状态码
1XX:服务端已经接收到了你的请求正在处理,客户端可以继续发送或者等待
2XX:200 OK 请求成功,服务端发送了对应的响应
3XX:302(临时定向) 304(永久定向) 重定向(想访问网页A,但是自动调到了网页B)
4XX:403访问权限不够 404请求资源不存在
5XX:服务端内部错误
--------------------------------------------
200 OK:表示客户端请求成功。服务器返回的数据在响应体中。
404 Not Found:表示服务器无法找到请求的资源。通常是因为客户端请求的 URL 错误或请求的资源不存在。
500 Internal Server Error:表示服务器内部发生错误,无法完成请求。通常是因为服务器程序出现了异常。
401 Unauthorized:表示客户端请求的资源需要身份验证,但是客户端未提供有效的身份凭证。通常是因为客户端未提供正确的用户名和密码。
------------------------------------
80 http
1080 socks代理
443 https
3389 windows远程桌面
3389端口是 Windows远程桌面的服务端口号。通过远程桌面协议,
用户可以像操作本机一样操作远程的电脑 ,实现远程控制。
使用3389端口进行远程桌面连接需要在远程计算机上开启远程桌面服务,并设置允许远程连接。
1080端口是Socks代理服务的默认监听端口。Socks(Socket Secure)是一种网络协议,
.
.
.
代码判断一个数是否是质数
# 简单实现
def is_prime(n):
if n <= 1:
return False
for i in range(2, n):
if n % i == 0:
return False
return True
-----------------------------
# 高级一点
def is_prime1(n):
if n <= 1:
return False
for i in range(2, int(n ** 0.5 + 1)):
if n % i == 0:
return False
return True
print(is_prime(499))
.
.
.
.
.
.
.
跨域问题
从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域。
跨域是由浏览器的同源策略造成的。
前后端分离项目,由于前端项目启动的ip地址与端口 与 后端项目是不一样的 所以肯定会有跨域问题
解决办法:后端往响应头里面放东西 或者用第三方的中间件
.
.
.
.
.
.
.
锁相关总结 参考博客 https://zhuanlan.zhihu.com/p/489305763
.
.
.
常见sql查询语句
# 默认按order by字段升序排序 加descent 就变成降序排列
select name,birth from students where score>=80 order by birth descent
# 分组
select post as '部门',max(salary) as '最高薪资' from emp group by post;
# 统计每个部门的部门名称 和 部门下的员工姓名
select dep,group_concat(name) from emp group by dep;
# 子查询
# 连表查表
.
.
.
.
面试题
# 将字符串妆化成字典 {'k': 1, 'k1': 2, 'k2': 3, 'k3': 4} 形式
s1 = 'k:1/k1:2|k2:3|k3:4'
d1 = {kv.split(':')[0]: int(kv.split(':')[1]) for kv in s1.replace('/','|').split('|')}
print(d1)
# 给定两个列表l1 l2 找出两个列表中相同与不同的元素
l1 = [1, 2, 3, 4, 9]
l2 = [1, 2, 8, 5, 6]
common_list = []
for i in l1:
if i in l2:
common_list.append(i)
different_list = [x for x in l1 if x not in l2]
print(common_list)
print(different_list)
-------------------------------------------
# Python中内置的数据结构有几种?
1. 列表(list):一种有序的可变容器,其中的元素可以是任意类型。
2. 元组(tuple):一种有序的不可变容器,其中的元素可以是任意类型。
3. 字符串(str):一种不可变的序列类型,用于存储字符。
4. 集合(set):一种无序的可变容器,其中的元素不可重复。
5. 字典(dict):一种无序的可变容器,其中存储了键值对,用于快速查找。
6. 范围(range):包含数值序列的对象,通常用于循环。
-------------------------------------------
# 简单谈一下 深拷贝 浅拷贝 拷贝
浅拷贝:
浅拷贝是对 对象的每个属性依次复制,当属性的值是不可变类型时,复制的是对象的值
当属性的值是可变类型时,复制的是对象的引用!!!
深拷贝:
会完全复制出一个一模一样的对象,但是新对象与原对象不共享同一个内存地址
所以对于可变类型与不可变类型的数据,深拷贝都是完全复制出一份新对象,并且与原对象不共享内存地址
-----------------------------------------------
# 如何查看操作系统监听的端口
netstat -nlp linux 命令
动态查看系统应用进程和cpu使用情况 top
查看cpu信息 cat /proc/cpuinfo
查看内存信息的使用情况 free
查看磁盘空间使用情况 df -h
查看系统中所有的进程 ps aux
# 如何用python程序来收集这些服务器主机的信息?
可以使用第三方的库
subprocess模块 # 该模块的方法,可以查看当前主机的信息
paramiko模块 # 通过SSH连接远程Linux服务器,执行命今获取远程服务器的资源信息
------------------------------------------------
# 内网环境中 别人无法访问你的django项目 可能有那些原因
自己项目配置文件看下:
1. ALLOWED_HOSTS 设置允许哪些主机ip访问 看有没有问题 直接改成允许所有 = ['*']
2. 看下静态文件的路由配置对不对
3. 查看项目启动的ip地址是不是0.0.0.0 python manage.py runserver 0.0.0.0:8000
4. 检查防火墙是不是拒绝了其他电脑浏览器的请求,关闭防火墙或添加允许访问
.
.
.
docker 的出现解决了哪些问题
# 解决了应用程序本地运行环境与生产运行环境不一致的问题
# 让快速扩展、弹性伸缩变得简单
# 解决了应用程序资源使用的问题,docker会一开始就为每个程序指定内存分配和CPU分配
.
.
.
.
.
.
.
.
.
F与Q查询
当查询条件也就是等号右边不是明确的,查询条件也需要从数据库中获取 就需要使用F查询
# 查询库存数大于卖出数的书籍
res = models.Book.objects.filter(kucun__gt=F('maichu'))
# res = models.Book.objects.filter(Q(pk=1) | Q(price__gt=2000)) # |是or
当查询条件是 或 的时候 用Q查询,因为默认的filter参数都是且的关系
.
.
.
ORM查询优化
# 优化主要就是优化的sql执行的次数,优化查询数据的时间!!
only与defer 关键字 优化查询数据的时间
res = models.Book.objects.filter(price__gt=20) # 查出所有对象的所有字段数据
res = models.Book.objects.filter(price__gt=20).only('title', 'price')
# 查出所有对象的'title', 'price'与price字段数据 假如只需要这两个字段的数据,显然该查询用时少
# defer() 除了括号里的数据其他的都查
------------------------------------------------------
# select_related与prefetch_related 优化的sql执行的次数
res = models.Book.objects.filter(price__gt=20)
for obj in res:
print(obj.publish.name) # 当用书对象点关联的出版社表里面的数据时,每次查询都需要走SQL查询
# 通过外键将两个表连接起来了,查询的数据放到一条条数据里
res = models.Book.objects.filter(price__gt=20).select_related('publish')
for obj in res:
print(obj.publish.name) # 不再走SQL查询!!!
.
.
.
.
.
.
.
.
.
sql语句的慢查询优化
----------------------------------------------
# 1 慢查询日志记录 定位慢SQL
# 查看慢查询日志配置
SHOW VARIABLES LIKE 'slow_query_log%'
# 手动开启查询日志
SET GLOBAL slow_query_log = 'ON';
# 查看设置的超时时间,超过该时间就定义为慢查询
show variables like 'long_query_time'
# 设置超时时间,设置好后,需要在下次会话后才能生效!!!
SET GLOBAL long_query_time = 5
# 查看分析执行耗时有没有开启
show variables like '%profil%'
# 查看sql的运行时间
show profiles
-----------
# 2 查看sql语句的执行效率:
在sql语句的前面加一个 explain
运行的结果
type字段对应的值是range级别,及以上就可以接受
rows字段对应的值是 mysql估算要找到所需的记录,需要读取的行数
extra字段包含
查询的其他信息 Using index使用了覆盖索引 Using where使用了条件过滤
为经常需要排序、分组和联合操作的字段建立索引
为常作为查询条件的字段建立索引 等等
----------------------------------------------
----------------------------------------------
# 慢查询sql优化的思路 ⾸先要搞明⽩慢的原因是什么?
是查询条件没有命中索引?是加载了不需要的数据列?还是查的数据量太⼤?
这三个方向是最关键的!!!
# 1 查询条件where后面的字段如果没有命中索引,就会导致全表扫描!!!
修改语句或者修改索引,使得语句可以尽可能的命中索引
# 2 加载了不需要的数据列
重写sql语句,去掉多余查询的字段,增加索引覆盖的机率
# 3 查的数据量太⼤:
确定是否需要查询⼤量的数据,
优化sql语句,字段加索引,热数据加缓存,主从复制读写分离
如果表里的数据量太大,sql语句也无法再优化了,可以考虑分表
----------------------------------------------
----------------------------------------------
# 为经常需要排序、分组和联合操作的字段建立索引
因为如果按照没有索引的字段进行排序,会将要查的数据全部查出来,然后再排序
但是如果是按照索引的字段进行排序,因为索引本身是有序的,就可以先按索引排序后
然后按照普通索引依次获取到主键索引,然后再依次通过主键索引拿到对应的每一条数据
# 为常作为查询条件的字段建立索引 等等
----------------------------------------------
.
.
.
.
.
.
sql语句优化面试题
where 后面的条件字段只要没索引就是全表扫描!!!
select 后面的查询字段如果有索引,就可以不用回表,查询速度就会变快!!!
------------------------------------------
# 假设age字段是普通索引
select * from table where age > 20 limit 100,10
# 这条sql语句的底层并不是跳过offset⾏,⽽是取offset+N⾏,
# 然后放弃前offset⾏,返回N⾏
所以当offset特别⼤的时候,效率就⾮常的低下,limit 10000000,10
10000010个数据全查出来了,又舍弃前10000000
------------------------------------------
# 解决办法:
select * from table where age > 20 limit 1000000,10 # 优化成下面的!!!
select * from table where id in (select id from table where age > 20 limit 1000000,10)
# 这样虽然也load了⼀百万的数据,但是由于索引覆盖,
# 索引中包含了查询所需要的字段数据,不用回表了,所以速度也会很快!!!
# where 后面的条件字段只要没索引就是全表扫描!!!!
.
.
.
.
.
.
什么叫索引 与索引覆盖
# 索引是⼀种特殊的⽂件,索引是一种数据结构,它们包含着对数据表⾥所有行记录的引⽤指针。
# 更通俗的说,索引就相当于⽬录
b+树与b*的只有叶子节点才会存放真正的数据,主与支节点只会存放索引数据
索引有哪些优缺点?
优点:⼤加快数据的检索速度
缺点:会降低增/改/删的执⾏效率
当对表中的数据进⾏增加、删除和修改的时候,索引也要动态的维护
--------------------------------------
--------------------------------------
# 索引覆盖就是覆盖索引
覆盖索引(covering index)
指一个查询语句的执行只需要从辅助索引中就可以得到查询记录,-------------
而不需要回表,去查询聚集索引中的记录。可以称之为实现了索引覆盖。
因此我们需要尽可能的在 select 后只写必要的查询字段,以增加索引覆盖的⼏率。
---------
# 覆盖索引就是指索引中包含了查询所需要的所有字段数据,
# 这种情况下就不需要再进行回表查询了。
.
.
.
.
.
什么叫回表查询 如何避免回表 ?
# 回表查询 :
先通过普通索引的B+树,查询到该普通索引对应的主键值,再通过主键索引的B+树,
查询到该主键索引对应的行记录数据 就叫回表查询!!!
----------------------------------------
----------------------------------------
# 示范:表中有四条记录:
1, shenjian, m, A
3, zhangsan, m, A
5, lisi, m, A
9, wangwu, f, B
两个B+树索引分别为:
(1)id为PK,聚集索引,叶子节点存储行记录;
(2)name为KEY,普通索引,叶子节点存储PK值,即id;
普通索引无法直接定位行记录,需要扫码两遍索引树
select * from t where name='lisi';
---------------------------------------------
# 只要实现了索引覆盖就可以不需要回表了!!!
# 什么情况下就是索引覆盖了呢 ?
select id from t where name='lisi';
由于查询的值是id, 而id的值已经在name普通索引树上了,
因此可以直接提供查询结果,不需要回表
当SQL语句的所有查询字段(select列)和查询条件字段(where子句)全都包含在一个索引中,
便可以直接使用索引查询而不需要回表。
即在这个查询里,索引name已经 “覆盖了” 我们的查询需求id数据,故称为索引覆盖。
.
.
.
.
.
.
.
sql注入问题 及如何解决
SQL注入:利用特殊符号的组合产生特殊的含义,从而避开正常的业务逻辑
比如前端用户名输入框里面输入 jason' -- kasdjksajd 密码框里不输入
username='jason' -- kasdjksajd'
password=''
sql = " select * from userinfo where name='%s' and pwd='%s' " % (username, password)
# sql语句就变了 select * from userinfo where name='jason' -- kasdjksajd' and pwd=''
cursor.execute(sql) # 执行sql语句 会出sql注入问题
----------------------------------------------
----------------------------------------------
# 解决办法:
1 使用ORM语句 不用pymaysql模块执行原生sql语句
2 或者不要手动拼接字符串,交给excute方法,
sql = " select * from userinfo where name=%s and pwd=%s "
cursor.execute(sql, (username, password))
----------------------------------------------
.
.
.
.
.
.
.
rbac 是什么?
基于角色的访问控制 Role-Based Access Control
用户表 用户与角色多对多表 角色表 角色与权限多对多表 权限表
5张表实现对用户访问的控制,一般用户不直接与权限表关联 通过角色表与权限表关联
.
.
.
.
.
位运算
>> 右移动运算符:把左边的数字的各个二进位,全部右移右边位的位数,再转变成10进制数据
47>>3 # 结果是 5
00101111 整体往右移3位 变成了00000101 就变成10进制5
00000101
.
.
.
.
.
.
python代码防泄露方案
# 公司写的项目部署在客户机器上,源代码会泄露掉,怎么办
-方案一:项目在服务器上启动起来,项目代码直接全部删掉,因为项目已经加载到内存里面了,
项目只要不停,都没事,一停就废了
-方案二:将项目用pipinstaller 编译打包成可执行文件,别人要想破解要反编译,成本会比较高
-方案三:做到docker镜像中---》运行容器必须传一个》》- e password=公司的授权码
只需要在项目里面第一行通过request对象
取到环境变量Request.ServerVariables('password') 检查授权码是不是公司给的
如果不是,不让项目跑起来!!!
.
.
.
.
.
.
.
csrf 是什么
跨站请求伪造:一个假网站模拟真网站,朝真网站的后端发送请求
解决方案:
# 1 token串的校验:
在核心页面打开时,就在页面生成了一个隐藏的token串,
客户在该页面提交数据到后端的时候,先校验token对不对,token不对直接拒绝请求,
这样可以避免假网站的朝真网站发请求了
# 2 请求头的referer 的检验
但假网站也可能会伪造该请求头 所以作用可能不大!!!
.
.
.
global与nonlocal 申明变量
global作用: 局部名称空间修改全局名称空间的变量
nonlocal作用: 局部内层名称空间修改局部外层名称空间的变量
age = 18
def index():
name = 'jason'
global age
age = 20
print(age) # 20
def inner():
nonlocal name
name = 'kevin'
inner()
print(name) # kevin
index()
---------------------------------
# 补充一点:
while 循环 和 for循环 下面的代码不存在, 局部名称空间的概念的
def monkey_peach():
p = 1
# 调用zhen_chu_5函数 返回false就运行p = p + 1
# 然后继续走while这行的条件判断 触发zhen_chu_5(p)函数的再次运行!!!
while not zhen_chu_5(p):
p = p + 1 # while下面没有局部名称空间的概念,所以变量p的值直接改了!!!
print(p)
# 只有函数里面的代码有局部名称空间的概念!!!
.
.
.
python 常用的内置模块
# time # 提供和时间相关的一些功能
# datetime # 提供和时间相关的一些功能
--------------------------------------------
# os # 主要与操作系统打交道
os.mkdir() os.makedirs() # 创单级或多级目录
os.rmdir(r'd2\d22\d222') # 删除单极目录d222
os.removedirs(r'd2\d22\d222\d2222') # 只能删除空的多级目录!!!
os.listdir(r'路径') # 获取指定路径下的文件名称
os.remove(r'一般写绝对路径') # 只删路径的最后的那一个文件!!!
os.path.dirname(__file__) # 获取当前执行文件所在的目录路径
os.path.abspath(__file__) # 获取当前执行文件的绝对路径
os.getcwd() # 获取当前执行文件所在的目录路径
os.path.exists(r'D:\day19')) # 判断目录或文件是否存在
os.path.join(r'D:\day19',r'模块使用.py') # 路径拼接 会自动在两路径间加斜杠
os.environ # 获取系统环境变量
--------------------------------------------
# pathlib os模块的升级版 简化了很多操作 用起来更方便
import pathlib
from pathlib import Path
# 对于文件的操作要先用Path类括号里放路径,生成路径对象
file = Path(r"wuhu.txt")
pathlib.Path.cwd() # 获得当前执行文件所在的目录路径
PurePath(__file__).parent # 获取当前文件所在的上一级目录
Path(__file__).resolve() # 获取当前执行文件的绝对路径
Path(__file__).resolve().parent # 获取当前执行文件的上一级目录路径
file.state().st_size # 获得文件大小
file.is_file() # 检查路径是否为文件
file.mkdir() # 创建单级目录
file.mkdir(parents=True) # 创建多级目录
file.rmdir() # 删除单极目录
file.unlink() # 只删路径的最后的那一个文件!!!
file = Path(r'D:\pythonProject03\day19')
file.joinpath(r'模块使用.py') # 路径拼接
# 拿目录下的所有
file = Path(r"D:\pythonProject\20230420")
l1 = [i.name for i in file.iterdir() if i.is_file()]
print(l1) # 才能拿到之前os.dirlist('file')得到目录下文件名列表的结果
--------------------------------------------
# random 产生随机数、随机取样等操作
--------------------------------------------
# json 序列化模块
将对象序列化为字符串
json_str = json.dumps(dict1) # 操作数据用dumps
with open(r'a.txt','w',encoding = 'utf8') as f:
json.dump(dict1,f) # 操作文件用dump
将字符串反序列化为对象
同理json字符串直接反序列化用loads dict1 = json.loads(json_str)
如果是文件 就直接打开反序列化成字典了
with open(r'a.txt','r',encoding = 'utf8') as f:
dict1 = json.load(f)
--------------------------------------------
# pickle 序列化模块 用于json模块不能序列化的数据 用它来序列化
比如日期对象、
--------------------------------------------
# logging模块
是Python内置的标准模块,主要用于输出运行日志
--------------------------------------------
.
.
.
collections模块
collections模块在Python内置的数据类型的基础上,提供了额外的高性能数据类型。
collections模块的OrderedDict类构建的字典可以支持顺序
使用该模块可以大大简化Python代码,提高代码效率
# 1.命名元组:namedtuple 一个工厂函数用来,创建一个带名字的元组子类
qqqq = namedtuple('Points', ['x', 'y']) # 生成一个新的带名字的元组子类
p1 = qqqq(1, 2) # 生成命名元组对象
print(p1) # 结果为 point(x=1, y=2)
print(p1.x) # 1 可以通过对象点的方法 取值
----------------------------------------
# 2 deque 双端对列 是一个类,没有继承列表类
q = deque(['a', 'b', 'c'], maxlen=100)
# 生成的这个双端队列对象,可以从队列的左边弹出元素,也可以从队列的右边弹出元素
# 可以往该双端队列的左边添加元素,也可以往该双端队列的右边添加元素
# 这就流弊了通过双端对列既可以实现:
队列先进先出的效果: 元素从双端对列左边添加,从右弹出,就是队列效果
也能实现堆栈先进后出的效果:元素从双端对列右边添加,从右边弹出,就是堆栈效果
----------------------------------------
----------------------------------------
# 3.OrderedDict 有序字典类 继承类字典类
# 让有序字典也具有队列与队栈的效果!!!
od = OrderedDict(a=1, b=2, c=3, d=4,e=5)
print(od)
# OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)])
print(d.popitem(last=True)) # 先弹后放进的键值对 ('e', 5)
print(d.popitem(last=False)) # ('a', 1)
print(d) # OrderedDict([('b', 2), ('c', 3), ('d', 4)]
# 如果 last 值为true 先弹 最后放进的键值对
# 如果 last 值为False 先弹 最先放进的键值对
----------------------------------------
----------------------------------------
# 4.计数器类 继承了字典类
# 用于对不可变类型的数据计数,非常方便
from collections import Counter # 该计数器类也是继承了字典类!!!
res = 'abacababaccbcbaca'
print(Counter(res))
Counter({'a': 7, 'b': 5, 'c': 5})
t1 = [1,2,3,4,5,6,7,8,9,1,5,6]
print(Counter(t1))
# Counter({1: 2, 5: 2, 6: 2, 2: 1, 3: 1, 4: 1, 7: 1, 8: 1, 9: 1})
t2 = (1,2,3,4,5,6,7,8,9,1,5,6)
print(Counter(t2))
# Counter({1: 2, 5: 2, 6: 2, 2: 1, 3: 1, 4: 1, 7: 1, 8: 1, 9: 1})
----------------------------------------
.
.
.
.
哈希函数
不可变类型的数据 才能哈希!!!
一种将任意长度的输入消息压缩成某一固定长度的消息摘要的函数。
哈希函数所必须的性质:
可应用于任意大小的数据块。
产生定长的输出。
对任意给定的x,计算H(x)比较容易,用硬件和软件均可以实现。
.
.
.
.
.
.
.
.
.
数据库连接池的作用
# 池化技术,实现资源的复用,避免资源的重复创建与销毁带来的开销
(这里的资源所代表的有,数据库连接、进程、线程等等!!!)
数据库连接池也是一种池化技术
应用程序每一次向数据库发起增删改查操作的时候,都需要创建链接,在数据库访问量比较大的情况下
频繁的创建链接会带来比较大的性能开销,连接池的核心思想是应用程序在启动的时候,
初始化一个连接池出来,里面维持一定数量的与数据库的链接,这样当应用程序需要与数据库建立连接的时候
只需要从池中取出连接使用即可,使用完毕再将连接放回池中,
这样就避免了程序,每一次要操作数据库都要先和数据库建立连接,后续再销毁连接,所带来的资源开销
连接池有哪些参数:初始化连接数、最大连接数(如果连接数不够,后续获取连接的线程会阻塞)、
最大空闲连接数(没有请求时池中保留的最大空闲连接)、最小空闲连接数、
最大等待时间(连接池里的连接用完,后续线程要等待的时间超过该时间就会提示超时异常!!)
无效连接清除、
#总结 控制数据库最大的连接数, 避免了频繁连接数据库与断开数据库,影响系统性能
.
.
进程池与线程池的作用
# 线程池
# 频繁地创建线程既会消耗太多内存,也会耗时太多,线程池限制线程的数量上限
线程执行完一个函数后不会被销毁,而是会等待去执行下一个新的任务函数,从而减少资源消耗
--------------------------------------------------
# 进程池
限制进程的数量上限,减少内存的消耗等
进程执行完一个函数后不会被销毁,而是会等待去执行下一个新的任务函数
.
.
.
mysql 相关知识
# Innodb引擎:Innodb引擎提供了对数据库ACID事务的⽀持。默认用的它
并且还提供了⾏级锁和外键的约束。它的设计的⽬标就是处理⼤数据容量的数据库系统。
# MyIASM引擎(原本Mysql的默认引擎):不提供事务的⽀持,也不⽀持⾏级锁和外键。
# MEMORY引擎:所有的数据都在内存中,数据的处理速度快,但是安全性不⾼。
-------------------------------------------
.
.
.
mvcc多版本并发控制
数据库并发场景有三种,分别为:
读-读:不存在任何问题,也不需要并发控制
读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
写-写:有线程安全问题,可能会存在更新丢失问题,比如第一类更新丢失,第二类更新丢失
-------------------------------------------------------
MVCC+悲观锁:MVCC解决读写冲突,悲观锁解决写写冲突(写的操作,mysql会自动加行锁)
MVCC+乐观锁:MVCC解决读写冲突,乐观锁解决写写冲突(CAS版本号控制,更新前看下版本号对不对)
-------------------------------------------------------
# 多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制!!!
并发读写数据库时,读操作时不阻塞写操作,写操作也不阻塞读操作。
它的实现原理主要是依赖记录中的 3个隐式字段, undo log , ReadView 来实现的
--------------------------------------------
# 3个隐式字段:
TRX_ID # 事务id
ROLL_PTR # 回滚点指针 存的是上个版本的指针地址,通过它可以把undo log里不同版本的修改数据链接成一个链表
ROW_ID # 隐藏的主键
# 把这个链表就叫做版本链,根据这个版本链就可以找到这条数据的所有历史版本
# mvcc就是通过链表这种方式来维护多个版本的!!!
--------------------------------------------
# undo log 回滚日志
--------------------------------------------
# ReadView 开启一个事务后,进行快照读(不加锁的读)的操作的时候产生的快照,
什么情况下会进行快照读:select查询的时候,数据修改时,也可以通过设置让事务启动时就快照读
快照里记录了
# 1 活跃事务id列表:记录了快照读的时候,所有的正活跃事务id,活跃事务:是开启但未提交的事务
# 2 当前活跃的事务id里面,最小的事务id ,也就是事务id列表里最小的值
# 3 产生快照时,数据库分配给下一个事务的id值
# 4 产生快照时,当前事务的id(只有对表中的数据做改动时,才会为事务分配id,否则id默认为0)
--------------------------------------------
# 关键是要读取哪个版本的数据了???
用读取的版本数据的id 去ReadView里面匹配,来确定该版本数据可不可见,
如果不可见,就通过回滚指针,到undo log 里面获取上一个版本的id 再到ReadView里面匹配,
如果确定该版本可见,就把该版本数据读出来!!!
# 1 如果当前事务,读取了一个数据版本的事务id,与当前事务的id 值相等 说明在访问自己修改过的记录,
所以该数据版本,可以被当前事务访问,是可见的
# 2 如果当前事务,读取了一个数据版本的事务id,比活跃事务id列表里的最小值还要小,
说明该数据版本是已经提交的事务生成的,所以该数据版本,也是可见的
# 3 如果当前事务,读取了一个数据版本的事务id,比数据库分配给下一个事务的id值还要大,
说明该数据版本是由将来还没有启动的事务生成的,所以对于当前事务而言是看不见的
# 4 如果当前事务,读取的数据版本的事务id,在当前活跃的事务id列表里面:
说明该数据版本是由当前还没有提交的事务生成的,所以是不可见的
--------------------------------------------
# mvcc 就是通过版本链,再加上ReadView,来一起实现并发事务的访问的
--------------------------------------------
--------------------------------------------
# 注意可重复读隔离级别的mvcc多版本控制策略 与 提交读的隔离级别的mvcc多版本控制策略 是不一样的
# 可重复读隔离级别下 mvcc多版本控制策略:
假如当A与B事务一起开启后,并读了p数据,A事务改了p数据,并提交事务后,
B事务读的p数据还是老的p数据,虽然A事务已经提交了,对B事务来说没卵用
怎么通过版本链和ReadView来实现的 ?
见下图,
主要就是因为读的事务,只有在第一次读的时候,才会产生快照readview,
后面再读行数据时通过数据的事务id到快照里的去按规则判断,该数据师傅可见
--------------------------------------------
# 提交读的离级别下 mvcc多版本控制策略:
每次查数据的时候,都会进行快照读而产生快照,然后拿着行数据的事务id号,去刚刚产生的快照里面
去比对,然后判断出该行数据是否可见
# 总结:
可重复读隔离级别下,事务开启后,只有在第一次查数据的时候,会快照读产生快照,
后面再查数据不会再产生快照了,都是用的第一次的快照来判断行数据是否可见的
提交读隔离级别下,事务开启后,每一次查数据的时候,都会快照读产生快照,
然后用最新的快照判断行数据是否可见的
# 也就是不管是可重复读隔离级别,还是提交读隔离级别,对于判断行数据是否可见,的判断规则都是一样的
# 只是两种隔离级别下,在事务开启后查数据的时候,去生成readview快照的规则是不一样的
# 可重复读在一个事务里,快照只会生成一次
# 提交读在一个事务里,每次查数据都会生成一个新的readview快照,所以能更新活跃未提交事务的id列表
--------------------------------------------
整体通过回滚指针,链接成一个链表,形成版本链
.
.
可重复读隔离级别下:
假设:事务A主要是修改数据,事务B主要是查数据
当更新完事务A后name的值由北冥变成北冥-2并且也提交了,事务B所查到的数据 还是老数据name=北冥
事务A修改数据的时候会生成快照,所以A快照里面的任务id变成2,当前未提交的事务列表里面的也只有2
事务B第一次查询数据的时候,也会生成快照,B快照里面的任务id变成3,并且当前未提交的事务列表里面有2,3
.
.
事务A提交后,数据提交到数据库了,页记录里面该数据的事务id是2
但是事务B再次查询数据的时候,不会再快照读产生快照了,所以还是会拿当前行数据里面的版本id 2 去
事务B一开始得快照里面的,当前未提交的活跃事务列表里面[2,3]比较一下,
所以事务B得出的结论就是,该数据是由未提交的事务产生的,
虽然我们知道事务A已经提交了,
但是根据快照的判断规则,事务B只能得出的结论是 该数据是由未提交的事务产生的,因为列表里有 2 !!
就会通过回滚指针获取上一个版本的id 1 ,再到事务B的快照里面去比较,
确定该版本的数据是已提交的数据,产生的,所以就读出来了
也就是靠这个原理,在可重复的读的隔离级别下,解决了不可重复读的问题
因为对于当前事务比如说F 第一次查询数据,生成的快照里面的,当前未提交的活跃事务列表里面的其他事务,
即使后来其他事务提交了,但是事务F的快照里面的列表里面 这些事务的id还在里面了
所以事务F在读数据库行数据的时候,通过事务id到快照里面去判断的时候,还是会判断出这些事务版本的
数据是不可见的!!!!!
然后通过回滚指针一直找到,比列表里面最小的事务id还要小的,事务id版本对应的数据!!!
.
.
.
.
.
.
提交读隔离级别下:
关键点就是在提交读隔离级别下,事务B每次查询数据都会先进行快速读,产生一个快照,然后用行数据的事务id
去最新的快照里面去判断,这时候得出的结论就不一样了,为什么
因为再次查询时,产生的快照里面,当前未提交的活跃事务列表里面会更新,只剩下2了,事务A的修改数据提交后,
再快照读的时候,未提交的活跃事务列表里面就已经没有1了,所以后续事务B,在读数据库行数据的时候,
通过事务id到快照里面去判断的时候,事务1已经不在列表里面了,版本号时事务1的是可见的,所以就能读出来
所以在提交读的隔离级别下,事务B第二次读的时候,事务A提交的修改的版本数据,对于事务B来说是可见的
这也是为什么 提交读的隔离级别下 会出现不可重复的现象了!!!
.
.
.
.
.
.
.
.
.
B树 B+树 B*树 区别
# 补充树结构的每一个节点,是放在一个磁盘块里面,每个磁盘块的大小是固定的一般是16kb
# 一个节点就需要一次io
----------------------
个人理解 可能不对!!!
# 树结构的最后的叶子节点里存放的索引与数据的多少,一是由16kb限制,
# 二是由该叶子节点的上一层的指针,左右两边的关键字(键值),限制的
--------------------------------------------
# B树
B树中所有的节点,都可以存放数据与索引(键值+指针)
--------------------
# B+树
B+树索引 叶子节点有直接指向其他叶子节点的指针,而且是双下指针
只有叶子节点才会存放 键值+数据
其他节点只会存放索引(键值+指针)
--------------------
# B*树
在支节点也添加了指向相邻支节点的指针,而且是双下指针
只有叶子节点才会存放数据,其他节点只会存放索引
# 注意: 这里的数据有可能是表里面行数据本身,也可能是行数据对应的内存地址!!!
-------------------------------------------------
# 补充:
# 辅助索引在查询数据的时候,最后还是会借助于主键索引
# 辅助索引的特点是,树结构的叶子节点存放的是主键值!!!
# 所以根据辅助索引查数据,先要走辅助索引树拿到数据的主键值
# 再走主键索引树,根据主键值,找到叶子节点上的数据!!!
------------------------------------------
# 单一节点里面能够存储的数据越多,树的层级就越矮,查询数据的速度就越快!!!
------------------------------------------------
.
.
.
.
.
mysql 为什么使用B+树 而不是B树
# B树结构的好处:
B树的分支节点也可以存储真实数据与索引,所以把访问频繁的数据,放在靠近根节点的地方
这种情况下,热点数据的查询效率就会比较高
----------------------------------------
# B+树结构的好处:
由于B+树的分支节点只存放索引数据,所以支节点的分叉就会多很多,每个分支节存的索引数据也更多,
所以B+树层级对比B树层级也矮,所以一次读取可以在内存里面,获取更多的索引数据,提高了搜索的效率
而且B+叶子节点有双向指针,指向相邻的叶子节点,对于空间的利用率更高
所以B+树如果进行全数据的遍历,找到最下面的叶子结点,
然后通过链表的形式,顺序遍历,就能拿到全部的数据了,减少io次数,磁盘读写代价更低
如果是B树,想要遍历所有的数据,就需要将所有支节点都走一遍,加上B树的层级又高,io次数肯定更多
B+树的查询效率更加稳定,B+树更适合范围查找
但是B树如果进行全数据的遍历,需要对树的每一层都需要去遍历
.
.
.
InnoDB引擎 与 MyISAM引擎 的区别
# 主键索引的叶子节点保存的是行数据的物理地址,还是行数据本身?
# MyISAM索引文件和数据文件是分离的(非聚集),叶子节点保存的是行数据的物理地址
# innodb 叶子节点保存的是行数据本身
注意这里的data 可不一定代表的是行数据啊
在 MyISAM引擎里 data代表的是行数据的内存地址
在 innodb引擎里 data代表的是正真的行数据!!!由区别的
B+Tree 支结点不存储数据,只存键值+指针; 叶子结点不存储指针,存的是键值+data数据
.
.
.
.
.
.
MyISAM索引实现
叶结点的data域存放的是数据记录的地址。下面是MyISAM索引的原理图:
MyISAM的索引文件仅仅保存数据记录的地址,
在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,
只是主索引要求key是唯一的,而辅助索引的key可以重复
MyISAM中索引检索的算法为首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其data域的值,
然后以data域的值为地址,读取相应数据记录。
.
.
.
InnoDB索引实现
虽然InnoDB也使用B+Tree作为索引结构,但具体实现方式却与MyISAM截然不同。
叶结点data域保存了完整的行数据记录。这个索引的key是数据表的主键
可以看到叶结点包含了完整的数据记录,InnoDB的辅助索引data域存储相应记录主键的值而不是地址
.
.
.
.
.
.
面向对象知识总结
# 对象查找名字的顺序
先从自己的名称空间中查找,再去产生该对象的类中查找,再去继承的父类中找
-------------------------------------------------
# 多继承情况下父类中名字的查找顺序
菱形继承 广度优先(从左往右每条道走到闭环前的点,换下一条道,最后才会找闭环的点)
非菱形继承 深度优先(从左往右每条道走完为止)
# 对象.mro() 可以直接获取名字的查找顺序
-------------------------------------------------
# type与object的区别
object是基类,任何的类都是继承自它
# object是type产生的
# 但是type又继承了object
type是元类,任何的类都是type类产生的
------------------------------------------------
------------------------------------------------
# 子类重写父类的某个方法
class MyList(list):
def append(self, values): # 在子类里面再定义一个append方法
if values == 'jason':
print('jason不能尾部追加')
return
super().append(values) # 子类再调用父类的append方法,if不走就走它
obj.append(111)
obj.append(222)
obj.append(333)
obj.append('jason') # 就实现append方法不能往对象obj里面追加jason
print(obj)
-----------------------------
# 隐藏: 类在定义阶段 名字前面有两个下划线 那么该名字会被隐藏起来 无法直接访问
class Book:
__name = 'haha'
def __speak(self):
print('111')
def run(self):
print(self.__name) # 在内部可以直接,对象点名字拿到
self.__speak()
obj = Book()
# print(obj._Book__name) haha
# print(obj._Book__speak()) 111 需要点 _类名.__隐藏的名字 ,才能拿到
obj.run()
# 在类的外部,要想访问隐藏的名字,需要点 _类名.名字 ,才能拿到
# 但是在类的内部,可以直接对象点名字拿到
--------------------------------------------------
# 伪装: 将类里面的方法伪装成类里面的数据,调用被伪装的函数时就不用加括号,对象点函数名,就运行函数了
class Book:
@property
def speak(self):
print('hahaha')
obj = Book()
obj.speak # 直接运行speak函数了
--------------------------------------------------
.
.
.
.
.
面向对象的双下魔法方法
# 类中定义的双下魔法方法,都是重写了,父类object里已有的方法双下魔法方法,
# 不需要去调用,在特定的条件下会自动触发运行
------------------------------------------
__init__ # 类创建空对象后触发,init方法作用是:给空对象添加独有数据
__str__ # 对象被执行打印操作的时候自动触发
__call__ # 对象叫括号的时候自动触发
__getattr__ # 对象点一个不存在的名字时,自动触发
__getattribute__ # 对象只要点名字就会自动触发
# 对象只要点名字就触发,有个坏处,对象想拿自己名称空间有的名字,没法正常拿了!!!
# 而且有了__getattribute__,对象点不存在的名字,只触发它,不会再触发__getattr__了
# 如果想拦截属性,可以重写该方法。
__setattr__ # 对象点名字设置值或改值时,会触发
__enter__ # 当对象 被with上下文管理的开始,自动触发, 且方法的返回值会被as后面的变量名f接收
__exit__ # with下面的子代码运行完毕后,会自动触发
__call__ # 对像加括号会触发类里面的双下call方法, 类加括号会触发元类里面的双下call方法
__new__ # 类加括号,先触发元类的双下new,再触发类自己的双下init,并把空对象传给self行参
__get__
# 一个类拥有双下get方法,该类产生的对象,又是另一个类的类属性对应的值
# 那么另一个类或者类的对象去调用该类属性的时候,就会自动触发该类里面的双下get方法
------------------------------------------
# 代码示范:
class Book(object):
def __init__(self, name):
self.my_name = name # 会触发__setattr__方法
print('打印init')
def __str__(self):
return f'哈哈{self.my_name}'
def __call__(self):
print(f'嘿嘿{self.my_name}')
def __getattr__(self, item):
print(f'对象点不存在的名字触发---{item}')
# 对象只要点名字就触发,有个坏处,对象想拿自己名称空间有的名字,没法正常拿了!!!
# 而且有了__getattribute__,对象点不存在的名字,只触发它,不会再触发__getattr__了
# def __getattribute__(self, item):
# print(f'对象只要点名字就触发---{item}')
def __setattr__(self, key, value):
print('给对象设置值,或修改值时触发')
obj = Book('lqz') # 先触发__setattr__方法 再打印init
print(obj) # 哈哈lqz
obj() # 嘿嘿lqz
obj.lyf # 对象只要点名字就触发---lyf
obj.my_name # 对象只要点名字就触发---my_name
---------------------------------------------
class Desc:
def __get__(self, instance, owner):
return '哈哈哈 TestDes:__get__'
class Main:
des = Desc()
t = Main()
print(t.des) # main类的对象点des属性,就会触发Desc类里面的双下get
print(Main.des) # main类点des属性,就会触发Desc类里面的双下get
# 拥有双下get方法的类,产生的对象,是另一个类的类属性对应的值
# 那么另一个类或者类的对象去调用该类属性的时候,就会触发该类里面的双下get方法
---------------------------------------------
---------------------------------------------
# 面试题
补全Context类里面的代码,使得整体运行不报错
class Context:
pass
with Context() as f:
f.do_something()
-----------------------
class Context:
def __enter__(self):
return self
def do_something(self):
pass
def __exit__(self, exc_type, exc_val, exc_tb):
pass
with Context() as f:
f.do_something()
---------------------------------------------
# 面试题
用面向对象的方法,生成一个字典,并让该字典可以通过点的方式,给字典添加值改值与取值
注意:python 自带的字典类型是不支持通过字典点的方式来进行取值的!!
class Mydict(dict):
def __setattr__(self, key, value):
self[key] = value
def __getattr__(self, item):
return self.get(item)
obj = Mydict()
obj.name = 'lqz' # 对象点名字设置值或改值 ,会触发__setattr__
obj.age = 23 # 触发了__setattr__ , 就不会给对象设置值或改值了
print(obj.__dict__) # {} 可以看到名称空间是没有name与age的
print(obj.age) # 23 利用对象点不存在的名字
print(obj) # {'name': 'lqz', 'age': 23}
--------------------------------------------------
.
.
.
.
.
.
面向对象之 元类
# type方法是用来查看产生对象的类的名字
class A:
pass
class B:
pass
print(type(A), type(B)) # <class 'type'> <class 'type'>
print(type(type)) # <class 'type'> type类还是由type类产生的
-----------------------------
object 叫基类 所有类都继承它
type叫元类 所有类都是它产生的
-----------------------------
# 创建类的两种方式
# 方式一 直接定义
class Teacher:
pass
# 方式二 由元类产生
cls = type(name, (bases,), dict) # 传 类的名字、类的父类、类的名称空间 生成并返回一个类
-----------------------------
# 元类定制类的产生行为
类加括号生成对象 ,触发类里的__init__
# 元类加括号生成类的时候,会触发元类的__init__方法,该方法可以控制元类生成类的行为
# 元类定制对象的产生行为
对象加括号会触发 产生对象的类里面的 __call__
# 类加括号会触发 产生类的类(元类)里面的__call__
-----------------------------------------------
-----------------------------------------------
# 元类控制类的创建过程
# 需求:所有的类必须首字母大写 否则无法产生
class MyMetaClass(type):
def __init__(self, what, bases=None, dict=None):
print('what>>:', what) # Haha 类的名字
print('bases>>:', bases) # 类的父类
print('dict>>:', dict) # 类的名称空间
# 判断字符串首字母是否大写
if not what.istitle():
raise TypeError('懂不懂规矩 类名首字母应该大写啊!!!')
super().__init__(what, bases, dict) # 子类继承调用父类type里面的__init__方法
obj = MyMetaClass('Haha',(object,),{})
------------------------------------------------
------------------------------------------------
# 元类控制对象的创建过程
# 需求: 给对象添加独有数据的时候 必须采用关键字参数传参"
class MyMetaClass(type):
def __call__(self, *args, **kwargs):
if args: # 如果args接收到了值,说明用户有用位置参数传参了
raise TypeError("Jason要求对象的独有数据必须按照关键字参数传参!!!")
return super().__call__(*args, **kwargs)
class Student(metaclass=MyMetaClass):
def __init__(self, name, age, gender):
self.name = name
def __call__(self):
print(888)
obj = Student(name='jason',age= 18,gender= 'male')
# 该语法先触发元类里面的__call__方法,把括号里面的参数交给__call__去处理
print(obj.__dict__) # {'name': 'jason'}
obj() # 888
------------------------------------------------
# 总结
# 当我们在py文件里面定义了一个类以后,并且代码执行到了定义类的代码后
# 会触发元类里面的双下init,来控制类的生成
--------------------------------
# 类加括号生成对象,会先执行元类的双下call方法,
# 双下call方法里面干了3件事:
# 1 产生一个空对象(元类产生的类点自己的__new__方法)
# 2 调用类里面双下init(元类产生的类点自己的__init__方法)
# 3 返回实例化的对象
------------------------------------------------
.
.
.
.
.
什么是反射,python中如何使用反射
-反射:是程序在运行过程中,通过字符串来操作对象的属性和方法
getattr(obj,'name')
# 根据字符串获取对象与它的类里,对应的属性的值或方法对应的值(函数体代码的内存地址)
setattr(obj,'name','value')
# 根据字符串给对象设置属性,或修改对象的属性(只作用在自己的名称空间)
hasattr(obj,'name') # 判断对象与它的类里,是否有某一个字符串对应的属性或方法
delattr(obj,'name') # 根据字符串删除对象里面的名字,注意不是删类里面的名字!!!
--------------------
class Book:
name = '宝贝'
def speak(self):
print('hahaha')
obj = Book()
print(hasattr(obj, 'speak')) # True
getattr(obj, 'speak')() # hahaha
setattr(obj, 'name', '畜生') # 对象的名称空间设置了name='畜生'
print(obj.name) # 畜生
setattr(obj, 'speak', '狗') # 对象的名称空间设置了speak='狗'
print(obj.speak) # 狗
delattr(obj, 'name') # 对象名称空间删除了name='畜生'
print(obj.name) # 宝贝 拿的是类里面的name对应的值
delattr(obj, 'speak') # 对象名称空间删除了speak='狗'
obj.speak() # hahaha
.
.
.
.
.
.
.
.
redis 的使用
五大数据结构:字符串、列表、哈希、集合、有序集合
3种特殊类型:BitMaps位图 、HyperLogLog基数统计 、GEO地理位置
redis通过pipeline管道 实现事务
.
.
.
.
.
.
.
装饰器的种类
当有多层装饰器: 加载顺序自下而上,执行顺序自上而下 # 直接记,原理有点烦
---------------------------------------
---------------------------------------
# 无参装饰器
@outer # register = outer(register)
def register():
pass
# 有参装饰器
def outer_plus(mode):
def outer(func_name):
def inner(*args, **kwargs):
res = func_name(*args, **kwargs)
return res
return inner
return outer
@outer_plus('lqz') # register = outer(register)
def register():
pass
# @outer_plus('lqz')的运行结果还是 @outer
----------------------------------------
----------------------------------------
# 1 函数装饰器
@outer # register = outer(register)
def register():
pass
-----------------------------------------
# 2 类作为装饰器
class Wrapper():
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
# 前面加代码
print('我在你前面')
res = self.func(*args, **kwargs)
# 后面加代码
print('我在你后面')
return res
@Wrapper # add=Wrapper(add)
def add():
print('add')
# Wrapper(add) 触发Wrapper类的双下init 并给类的对象添加了func方法
# 现在add是Wrapper类的对象
add() # 对象加括号触发类里的双下__call__ 运行func方法就是运行被装饰的函数
-----------------------------------------
# 3 加在类上面的装饰器(可以用装饰器给对象添加一些独有的属性或方法)
def speak():
print('说话了')
def wrapper(cls):
def inner(*args, **kwargs):
res = cls(*args, **kwargs)
res.name = 'lqz'
res.speak = speak
return res
return inner
class Animal:
pass
@wrapper # 语法糖会把Person当参数传入到装饰器中 Person=wrapper(Person)
class Person(Animal):
def __init__(self, hobby):
self.hobby = hobby
p = Person('篮球')
print(p.name)
p.speak()
print(p.hobby)
# 类有继承父类,也一样,装饰器也能正常用
.
.
.
.
.
.
什么叫 缓存穿透 缓存击穿 缓存雪崩
---------------------------------------------------
# 缓存穿透是指:
缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,
造成数据库短时间内承受⼤量请求⽽崩掉。
# 解决⽅案
1. 业务逻辑前置校验 如请求携带的数据是否合理
2. 从缓存取不到的数据,在数据库中也没有取到,将其缓存设为空值 key-null或默认值,并设置一个过期时间
3. 访问频率限制,同一个ip或者id一分钟内最多访问多少次,防⽌攻击⽤户暴⼒攻击
# 使用布隆过滤器(重要)
4. 在写入数据时,将键使用布隆过滤器进行标记(相当于设置白名单),
查询业务请求发现,缓存中无对应数据时,可先通过查询布隆过滤器,判断数据是否在白名单内,
如果不在白名单内,则直接返回空或失败,不让走数据库了!!!
5. 用户黑名单限制
---------------------------------------------------
---------------------------------------------------
# 缓存雪崩是指:
缓存中大量热点数据同时过期,后⾯的请求都会落到数据库上,
造成数据库短时间内承受⼤量请求⽽崩掉。
# 解决⽅案
1. 缓存数据的过期时间设置偏长点并且是随机数,防⽌同⼀时间⼤量数据,过期现象发⽣。
2. 热点数据可以设置永不失效等
3. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同的缓存数据库中。
--------------------------------------------------
--------------------------------------------------
# 缓存击穿是指:
单个热点数据,在不停的扛着大并发,在这个热点数据失效的瞬间,持续的大并发请求,直接请求到数据库,
导致数据库压力骤增,这种情况就是缓存击穿
# 解决⽅案
热点数据设置永不过期
在value内部设置一个比缓存(Redis)过期时间短的过期时间标识,当请求获取缓存的value值时,
发现该值快过期时,马上延长内置的这个时间,并重新从数据库加载数据,设置到缓存中去。
给查redis缓存数据的操作加个互斥锁,牺牲一点redis的查询效率
这样第一个请求查redis没有,到数据库查,查出数据更新到redis
后续请求拿到锁后,再查redis时,已经能拿到数据,这样不会去数据库查了
.
.
.
.
布隆过滤器原理与使用
.
.
.
.
redis 使用 常用命令
注意在python中用的操作redis的命令,和直接在redis客户端的命令是不一样的
# python中使用普通连接
from redis import Redis # 1 导入模块的Redis类
conn = Redis(host='127.0.0.1', port=6379) # 2 实例化得到对象
conn.set('age',19) # 3 设置值
conn.close()
---------------------------------------------
---------------------------------------------
# python中使用连接池连接 先单独建个文件,把连接池对象做成单例
import redis
POOL = redis.ConnectionPool(max_connections=100, host='127.0.0.1', port=6379)
# 创建一个最大连接数为100的redis连接池
---------------
import redis
from pool import POOL
# 做成模块后,导入,无论导入多少次,导入的都是那一个POOL对象
# 假设该方法是写在视图类或者视图函数中的
def task():
# 从池里面拿一个连接
conn = redis.Redis(connection_pool=POOL)
# 如果报错了,可能是连接池的数量设置的太小,线程太多导致
print(conn.get('name'))
---------------------------------------------
---------------------------------------------
import redis
conn = redis.Redis()
# 字符串相关操作
conn.set('hobby','篮球',ex=3) # 设置键hobby 值'篮球' 过期3秒
conn.set('name','lqz',nx=True) # 有键对应的值就不修改,没有就设置该键值对进去
conn.get('hobby') # 获取值
res=conn.strlen('hobby') # 统计值对应的字节长度
conn.decr('age',2) # 自减2 不写数字就自减1
conn.incr('age',2) # 自增2,不写数字就自增1
------------------------------
# 列表相关操作
res = conn.lpop('girls') # 从左侧弹出数据
res = conn.llen('girls') # 列表里面数据个数
res = conn.lrange('girls', 0, 1) # 前闭后闭区间 按索引区间拿多个值
res = conn.lrange('girls', 0, -1) # 拿列表里所有值 但列表里的元素是二进制的形式
conn.lpush('girls', '刘亦菲', '迪丽') # 从左侧插入 redis默认是从列表右侧插入
res = conn.lrange('list1', 0, -1)
print('11'.encode('utf8') in res) # 判断元素'11'在不在列表里
------------------------------
# 哈希相关操作
conn.hset('userinfo','name','lqz') # 给userinfo对应的字典设置键与值
res=conn.hget('userinfo','name') # 拿值
res=conn.hmget('userinfo',['name','age']) # 批量拿值
res=conn.hlen('userinfo') # 获取字典长度
------------------------------
# 集合相关操作
res = conn.sismember("test", "6") # 判断元素6在不在 名字叫test的集合里面 在就返回true
res = conn.sinter(["tag1","tags2"]) # 返回名字叫tag1、tags2集合的交集
------------------------------
# 其他操作
res=conn.type('girls') # 判断数据的类型
res=conn.randomkey() # 随机弹出一个数据的名字(键)
res=conn.keys('*') # 拿库里面所有数据对应的名字
res=conn.keys('w?') # 拿库里面所有数据对应的以w开头,后面只有一个任意字符的名字
res=conn.exists('userinfo') # 判断库里面有没有userinfo名字的数据
.
.
.
.
redis客户端里常敲的命令
expire(name ,time) # 设置数据的过期时间(秒)
TTl(name) # 获取键对应数据还有多长时间过期
setex name 过期时间S value # 添加值,并添加过期时间
keys * # 查看所有键
exists name # 如果直接判断名字对应的数据在不在
.
.
.
.
.
.
redis的管道
# 事务---》四大特性:原子性、一致性、隔离性、持久性
# redis通过管道可以支持事务!!!
# 集群的redis是不支持事务的
(多机集群情况下,每台机器都起来redis服务,但所存放的redis数据是不一样的,都加起来,才是总数据)
# 只有单实例,才支持所谓的事务(单实例是只有一台机器,只跑了一个redis的服务)
# 支持事务是基于管道的
------------------------------------------------
-执行 张三转账给你 的命令 一条一条执行
张三 金额 -100 conn.decr('zhangsan_je',100)
你 金额 100 conn.incr('lisi_je',100)
# 但是现在,在张三转弯帐后,突然断电程序挂了,你的账户没加上去,出现了数据问题了
# 当把这两条命令,放到一个管道中,先不执行,当执行excute,一次性执行多个命令
------------------------------------------------
------------------------------------------------
# redis中管道如何使用
import redis
# 模拟转账操作
conn = redis.Redis(host='127.0.0.1', port=6379) # conn是Redis类产生的连接对象
p_obj = conn.pipeline(transaction=True) # 开启事务 pipeline方法调用后返回的是管道对象
p_obj.multi() # 提供一个管道
p_obj.decr('zhangsan_je', 100)
# raise Exception('断电或程序崩了') # 这样后程序崩了,zhangsan数据就不会减了
p_obj.incr('lisi_je', 100)
p_obj.execute() # 一次性把管道里所有命令,按顺序全部执行,期间有一个命令执行失败,回滚到开始
conn.close()
# redis通过管道实现 支持事务
------------------------------------------------
.
.
.
redis为什么并发量高和速度快
纯内存的操作,读写速度非常快
单线程避免了线程间的切换
io多路复用,一个线程可以接受特别高的并发
.
.
.
redis的长慢命令优化
# 单线程架构,命令一个个执行
# 如果有长慢命令,会造成整个redis的阻塞
# 设置一下慢查询队列的最大长度,以及最大查询时间,
# 查询语句的查询时间超过该时间,就会放到慢查询日志里面去
slowlog get # 获取慢查询队列的所有慢命令
# 哪些命令会造成慢查询
使用复杂度过高的命令: O(N)以上的命令(求两集合的并集、数据的排序等命令)
复杂度过高的命令,底层代码的执行次数多,所以更耗时
大value问题: value的值过大 key对应的值尽量避免写入过大
数据集中过期: 将过期时间随机化,分散集中过期的时间,降低 Redis清理过期 key的压力
.
.
.
Bitmap的使用原理
位图的使用
假设:1亿用户,5千万活跃用户
要统计每一天的活跃用户是多少,怎么办?
首先每天按日期在redis里面创一个对应的字符串
比如 set data1 c # 随便设置个简单的字符串 c
每天0点计数器归零,每来一个用户登录 setbit data1 n 1
# 给位图对象按计数器数字,给对应位置设置为1
# 这样一天下来,只要用 bitcount data1 统计一下该字符串里面有多少个1
# 就是今天所有的登录用户数了
.
.
.
.
.
redis的集群
.
.
.
redis 怎么持久化
使用RDB 或者AOF 或者混合持久化方案来实现
都是要在redis的配置文件里面,配置一些参数,设置触发持久化方案的条件
RDB文件恢复到内存的速度快,但存在丢失数据的风险
aof文件,设置每秒从缓冲区刷到硬盘,这样系统崩了,也只会丢一秒的数据,
但是aof文件恢复到内存的速度慢,要一条一条的执行
混合持久化的方案,是每次aof重写的时候,拍个快照生成rdb数据,并将这些数据,写入新建的AOF文件中
在AOF重写开始之后,执行的Redis命令,则会已aof记日志的形式,
追加到刚刚新建的AOF文件中的rdb数据的后面
# redis持久化过程中出错了怎么恢复数据
重启redis就行了,redis会自动加载持久化文件到缓存里面去的
# 换句话说,在开启了RDB-AOF混合持久化功能之后,服务器生成的AOF文件将由两个部分组成,
其中位于AOF文件开头的是RDB格式的数据,而跟在RDB数据后面的则是AOF格式的数据
# Redis服务器启动并载入AOF文件时,它会检查AOF文件的开头是否包含了RDB格式的内容
# 如果包含,那么服务器就会先载入开头的RDB数据,然后再载入之后的AOF数据
# 如果AOF文件只包含AOF数据,那么服务器将直接载入AOF数据
.
.
.
.
.
django中使用mysql 的主从与读写分离
.
.
.
.
.
.
.
黑白名单机制实现
# 白名单机制实现:
比如项目ip地址的白名单,公司内部的系统,不是公司的内部的ip地址不能访问
(一般是用redis的集合,在中间件或者权限类里面检查ip地址在不在集合里面
sismember set88 v1 )
# 黑名单机制实现:
检测到一些恶意ip,直接放到黑名单里,拒绝该用户访问!!! 也是用redis的集合做方便
.
.
.
.
cookie session token 大致原理
session 是存在后端的 cookie是存在前端的
-------------------------------------------
-------------------------------------------
# cookie
账户密码校验没问题后,生成cookie,放到重定向对象里
obj = redirect('/home/') # 重定向的整体代码看成是一个对象
obj.set_cookie('name', username) # 添加cookie信息到对象
return obj # 执行重定向
后端将cookie放在响应头里面,返回响应对象给前端浏览器,浏览器从响应对象里面,拿出响应头里的cookie
放到浏览器里面
浏览器会将存在浏览器里面的所有cookie,在发送请求的时候,会放到请求头里面
另一个请求到后端后,从请求头里面,获取name键对应的值,有的话就认为登录了
request.COOKIES.get('name'):
-------------------------------------------
-------------------------------------------
# django 中有一个session表
登录成功后
request.session['key'] = value # 生成随机字符串 与加密的value 放到session表中
django的中间件会自动将随机字符串,放到响应头里面,发送给前端,浏览器拿到头里面的session字符串存起来
当前端请求头中带着该随机字符串,到后端后,
中间件会自动获取请求头里面的 随机字符串,去django_session表查下有没有该随机字符串
要是有,再根据该随机字符串 拿session表中加密的数据
并自动解密数据并处理到request.sesion.get()中
# 如果中间件中,发现请求头中的随机字符串,在session表中没有,说明随机字符串有问题
# 在中间件就返回了,进入不了视图函数了
-------------------------------------------
-------------------------------------------
# token机制
django有自带的jwt登录认证类
token的使用大致流程:
一般情况下,用户登录时,如果用户名与密码校验没问题后,
用token机制的话,先用该用户对象生成荷载
再用荷载生成token
最后将token返回给前端
不管jwt自带的认证类还是自己写的认证类,都会有如下操作:
payload = jwt_decode_handler(token)
# 重点代码 用token获取荷载,并且还校验了token串里面的签名是否正确
就是用秘钥将token中解码出的头与荷载,用同样的算法生成签名,
与token里解析出的签名比较,判断签名是的正确,签名通过
然后再用荷载拿到用户id,通过ORM可以拿到用户对象了
最后 认证类的authenticate方法 返回用户对象与token
用户对象与token 后续又解压赋值给了request对象的 user 与auth 属性
所以只要请求过了用户登录认证类,
就可以用request对象去点user拿到当前登录用户的数据库对象
点auth就能拿到token
-------------------------------------------
.
.
.
.
21 种设计模式
何为设计模式?
设计模式的分门别类
2、工厂方法(Factory Method)模式
.
.
单例(Singleton)设计模式
# 4、单例(Singleton)设计模式
from threading import Thread, Lock
import time
# 1 利用装饰器来实现,每一个想实现的单例的类,在上面加个该装饰器函数就行
def singleton(cls):
_instance = {}
lock = Lock()
def wrapper(*args, **kwargs):
if cls not in _instance:
lock.acquire()
if cls not in _instance:
_instance[cls] = cls(*args, **kwargs)
lock.release()
return _instance[cls]
return wrapper
# 这个地方 为什么要判断类的属性是否为空? 高并发的情况下 如果前几个子线程都先判断出_instance为None
# 那么就都会对象实例化,生成的对象就不一样了 但是让先判断出_instance为None的子线程,再抢锁,枪完锁后,让类生成对象
# 这这样第二个子线程在抢到锁后,再判断时,_instance已经有值了,就在放锁了,其他执行慢的子线程,在走第一个判断时,发现有值
# 直接连抢锁的操作都不走了!!后面的子线程就都没有抢锁放锁的操作了,直接返回对象了
# 这个地方如果先定义_instance = None 不让等于空字典 那么就要在闭包函数里面先申明该变量是nonlocal
# 内层局部名称空间直接修改外层局部名称空间中的数据,要在内层对变量先用nonlocal申明
@singleton
class B:
def __init__(self):
time.sleep(1) # 模拟并发情况下,对象实例化时有网络延迟
def __getattr__(self, attr):
return attr
def task():
obj = B()
print('\n')
print(id(obj))
for i in range(50):
t = Thread(target=task, )
t.start()
---------------------------------------------
from threading import Thread, Lock
import time
lock = Lock()
# 2 在类里面写一个绑定给类的方法,并且在类里面先写一个类属性_instance = None
# 这样在类的方法里去做判断,类属性有值,就直接返回类属性,如果类属性没有值,就让类名加括号传init需要的参数
class B:
def __init__(self, name):
self.name = name
time.sleep(1) # 模拟并发情况下,对象实例化时有网络延迟
_instance = None
@classmethod
def singleton(cls):
if cls._instance is None:
lock.acquire()
if cls._instance is None:
cls._instance = cls('teng')
lock.release()
return cls._instance
def task():
obj = B.singleton() # 该方法的返回值,对象就是单例
print(id(obj))
for i in range(5):
t = Thread(target=task, )
t.start()
-----------------------------------
.
.
.
.
5、生成器(Builder)模式
-----------------------------------------------
10、观察者(Observer)模式
定义:观察者(Observer) 模式定义对象间的一种一对多的依赖关系,
当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
-----------------------------------------------
12、迭代器(Iterator Pattern)模式
14、装饰器(Decorator)模式
-----------------------------------------------
15、责任链(Chain of Responsibility)模式
16、状态(State Pattern)模式
17、策略(Strategy)模式
18、命令(Command) 模式
19、享元(Flyweight)模式
20、代理(Proxy Pattern)模式
21、备忘录(Memento Pattern)模式
1、原型(Prototype)模式
3、抽象工厂(AbstractFactory) 模式
6、适配器(Adapter Pattern)模式
7、桥接(Bridge)模式
8、外观(Facack) 模式
9、中介者(Mediator)模式
11、组合(Composite)模式
13、访问者(Visitor Pattern)模式
.
.
.
.
mysql表如果没有设置主键
如果有一个字段是唯一索引,会用该字段作为主键
如果也没有唯一索引,会生成一个隐藏主键,目的就是为了用该隐藏主键构建B+树
.
.
.
.
.
python基础
求结果:or and
v1 = 1 or 3 # 1 or 是左边符号就取左边
v2 = 1 and 3 # 3 and是两边都符合取右边的
v3 = 0 and 2 and 1 # 0 如果有不符合直接取不符合
v4 = 0 and 2 or 1 # 1 or 有符合的就取符合的那一边
v5 = 0 and 2 or 1 or 4 # 1
v6 = 0 or False and 1 # False
.
.
.
.
.
字符串
s1 = 'helloworld' s2 = 'kk'
s1[1:5] # 左闭右开 从索引1一直切取到索引4 结果 ello
len(s1) # 求长度
s1.strip() # 移除字符串首尾指定的字符 不写就是移除空格
s1.split('elloworl') # 切割掉字符串中指定的字符,剩下的部分放在元组里面 ['h','d']
s1.upper() # 字符串字母转大写
s1.replace('elloworl','ar') # 替换
'aaa'.join([s1,s2]) # 字符串件插入并拼接
res = 'helLO wORld hELlo worLD'
print(res.index('wORld')) # 找不到直接报错
print(res.find('wORld')) # 找不到默认返回-1 find用的比较多!!!
.
.
.
.
列表 的常见使用场景
可以肤浅的将列表理解为一个无限长,无限宽的抽屉,它可以装任何我们想要装的元素(自动扩容),
并且所有装进去的元素都是根据装进去时间先后排序的
列表可以将大量的任务装在其中,通过循环我们可以取出其中的任务,并且使用相同的代码结构来处理任务
可以简化我们的代码量
l1 = [111, 222, 333, 444, 555, 666, 777, 888] l2 = [999,]
l1[0] = 123 # 按索引修改列表数据
l1.append('干饭') # 尾部追加
l1.insert(0, 'jason') # 按索引位置往列表插入数据 原数据整体往后
l1 + l2 # l2列表里数据整体加到l1列表里
l1.remove(444) #移除
l1.pop(1) # 按索引弹出
l1.sort(reverse=True) # 改成降序排序
.
.
.
.
元组 的应用场景
t1 = (11, 22, 33, 44, 55, 66)
# 元组内索引绑定的内存地址不能被修改!!!
# 元组不能新增或删除数据!!!
# 列表中的元素可以修改,而元组之中的元素不可以修改。
# 元祖的应用场景 1 打包解包 2 让函数返回多个值
# 把多个用逗号分隔的值赋给一个变量时,多个值会打包成一个元组类型;
# 把一个元组赋值给多个变量时,元组会解包成多个值然后分别赋给对应的变量,
# 函数的返回值是多个值的时候,会组装成一个元祖返回
t = 11, 22, 33, 44, 55, 66
print(type(t)) # <class 'tuple'>
x, y, z, i, j, k = t
print(x, y, z, i, j, k)
.
.
.
.
.
.
字典 的应用场景
字典(Dictionary)是一种无序的数据结构,用于存储键值对(key-value pairs)的集合。
每个键(key)都是唯一的,而对应的值(value)可以重复。
字典的特点是使用键来访问值,而不是使用索引。
user_dict = {'username': 'jason','password': 123}
user_dict.get('username') # 取值
user_dict['username'] = 'tony' # 该值
res = user_dict.pop('password') # 将字典里面键对应的一组键值对都弹出字典了 res接收到弹出的值
del user_dict['username'] # 直接将这一组键值对都删除了
user_dict.popitem() # 弹出键值对 先弹最后后放进去的!!
# 字典三剑客 得到的数据都在列表里面!!!
user_dict.keys() # 一次性获取字典所有的键
user_dict.values() # 一次性获取字典里所有的值
user_dict.items() # 一次性获取字典里所有的键值对数据,放在一个个元组里
.
.
.
.
.
.
for循环注意问题
# 列表外给定一个值,当要删除列表里多个和该值相同的值时
# 无论for循环的是列表的索引还是列表的值,也无论用的是列表点pop(索引)删 还是 点remove(值)删
# 都有问题,删掉一个数据后列表的长度就变短了,最后都会报索引超出范围的错!!!
-----------------------------------------------
# for 是按索引位置依次拿值的,依次拿索引的 0 1 2 3
a = [1, 2, 3, 4, 5]
for i in a:
a.remove(i)
print(a)
# 第一次 [2, 3, 4, 5] 第一次删索引0的位置,这个时候列表已经变了
# 第二次 [2, 4, 5] 第二次删索引1的位置,上面列表删1的位置删的3
# 第三次 [2, 4] 第三次删索引2的位置,同理删上面列表2的位置删的是5
# 所以用for循环 是删不干净列表里的值的
------------------------------------------------
# 删除列表里的1元素
l1 = [1, 1, 12, 3, 4]
for i in range(5):
if l1[i] == 1:
l1.pop(i)
print(l1)
# 会报错 IndexError: list index out of range
# 列表超出索引的范围,为什么?
# i一开始为0的时候,移除了列表的0位的1,这时候列表其他位置的数据会往前移动变成 [1, 12, 3, 4]
# 此时该列表的索引1的位置是12 不符合 , 索引2的位置是3 也不符合
# 索引3的位置是4 也不符合 此时的列表没有索引4,
# 所以range最后一次for循环出的4 已经超出了列表的索引范围
------------------------------------------------
# 同理用remove也是不行
a = [1, 1, 2, 1, 3, 4, 5]
for i in a:
if i == 1:
a.remove(i)
print(a) # [2, 1, 3, 4, 5]
------------------------------------------------
.
.
.
.
.
.
.
导入问题 重要
# 在一个s20文件里创建对象
class B:
pass
b = B()
---------------
# 在另一个s21文件里导入该对象
from s20 import b
---------------
# 再在另一个文件里面,给对象添加私有属性
from s21 import b
b.name = 123 # 每一个py文件运行完,所有存储在内存中的数据全部会丢失
---------------
# 再在其他文件里面,拿对象的属性
from s21 import b
print(b.name) # 拿不到 会报错 每一个py文件运行完,所有存储在内存中的数据全部会丢失
-----------------------
# 所以要想实现跨文件给对象添加值,与获取值, 需要把文件写在项目里
# 项目启动以后不停,就可以实现跨文件给一个对象,添加值,与修改值
# 如果是一个项目,项目运行起来后,会把所有的文件,能运行的都运行一遍,所有文件在一个进程里面
# 所以根据同一个文件不会被导入两次的原理,在一个项目的不同文件夹下,导入的对象,都是同一个对象
# 如果不是一个项目,单py文件运行,该py文件就是一个进程,文件运行结束,进程里面的数据就被回收了
# 所以单py文件运行一次后,导入的对象就被销毁,另一个单py文件再运行,导入的对象又是一个新的对象了!!
-----------------------
.
.
.
.
.
双token认证
pip3 install djangorestframework-simplejwt -i https://pypi.douban.com/simple
# 该模块中提供双token
在登陆成功的时候,
后端返回两个token给前端,一个是有效期长的token,一个是有效期短的token给前端
登录成功后,前端发送请求都是带着有效期短的token,去后端进行校验,有效期短的token一旦过期
前端就需要再带着有效期长的token去后端校验,后端确认长token没有问题后,
再签发一个短token给前端,然后前端再带着短token去发送请求。
# 长token只有在登录成功后,从后端发给前端 及短token过期后,从前端发送到后端
# 被抓包软件抓到的概率会低一点
----------------------------
# 还有一种方式,让生成的token与登录时的ip地址做绑定,这样别人就算抓到token,
# 他的电脑ip的地址还是过不了认证!!! 除非别人又拿到你的token,又拿到你的ip地址,概率比较小
----------------------------
.
.
.
.
.
http请求的所有东西,都在request对象里面,本身是一个http请求,到了python中被封装成了request一个对象
视图函数最后返回的response对象里,包含了http响应的所有东西,
.
.
.
闭包函数参数延时性 问题
def func_outer():
func_list = []
for i in range(4):
def func_inner():
return i * i
func_list.append(func_inner)
return func_list
for func in func_outer():
print(func()) # 9 9 9 9
# 原因是:func_inner函数并没有被调用, 所以添加到列表里面的的4份func_inner函数还没有传值了
# 所以func_outer函数的执行结果就是,往列表里面加了4个func_inner函数
# 每一个func_inner函数运行的时候,需要用到变量i,此时func_outer函数里面的for循环已经执行完了
# 此时i变量名对应的值是3,所以每一个func_outer函数执行的都是,3 * 3 所以结果都是9
.
.
.
关于生成器表达式的一些注意问题
def aaa():
print(1)
l1 = [aaa() for i in range(4)] # 列表生成表达式
l2 = (aaa() for j in range(4))
# 这个特殊他是一个生成器,所以aaa()不会运行,必须要对生成器for循环的时候,才会执行aaa函数
print(l1)
print(l2)
for k in l2:
print(k)
.
.
.
关于匿名函数的调用问题
def aaa():
return [lambda x: x * i for i in range(4)]
# lambda匿名函数 整体类似于代表的是是一个函数名,不会被主动执行
# 需要用res = lambda x: x * i 接收再用res() 才能运行该匿名函数
# 内置函数中配合匿名函数使用时,底层会自动去运行匿名函数
# 所以列表里面是4个匿名函数 [ lambda x: 3x , 总共4个匿名函数 ]
print(aaa()) # 结果对应的是4个匿名函数的内存地址
print([m(2) for m in aaa()]) # [6, 6, 6, 6]
# 所以最后对列表for循环出的每一个,匿名函数加括号运行,并传2进去,最后的结果全是6
.
.
.
.
关于元祖的使用注意事项
a = (1, 2, 4, [1, 3, 5])
res = a[3].append(2)
print(res) # None
print(a) # (1, 2, 4, [1, 3, 5, 2])
a[3] = a[3].append(2) # 报错!!!
# 原因
# 元祖的元素是不可变类型,所以对应的元素是列表,你在列表里面添加数据不会报错
# 但是你要改变元祖里该列表元素的数据类型,就会报错了
.
.
.
.
.
.
.
.
彻底搞懂 迭代器,生成器,与可迭代对象
----------------------------------------------
# 迭代:一种不依赖于索引取值的方式,
可以通过 for循环 或者 点双下next() 一个个的取值,它就称之为 迭代
-----------------------------------------------
# 可迭代对象:
但凡对象内置有__iter__方法,并且返回值是一个迭代器对象, 那么该对象称为可迭代对象!
比如 字典,列表,字符串,元组,集合,文件对象
---------------------------------------------------
# 迭代器官方定义:
当类中定义了__iter__方法与__next__方法,并且__iter__方法方法返回了对象本身即self
每执行一次__next__方法,返回一个数据,如果没有数据了,则需要主动抛出一个异常
这个类就是迭代器类,
该迭代器类实例化生成的就是迭代器对象!!!
------------------------------------------------------
# 可迭代对象调用__iter__方法,为什么会变成迭代器对象
因为__iter__⽅法,返回了⼀个迭代器对象
------------------------------------------------------
# 生成器:生成器本质就是迭代器
函数中只要有yiele关键字,这个函数被加括号调用,它就变成了生成器!!!
---------------------------------------------------
# 生成器的作用:可以获得庞大的数据,同时占用内存小,节约内存空间
什么样的场景可以用到迭代器或生成器:
比如有一个数据量很大的数据源,数据源不在内存中,比如在数据库中,
现在要用sql语句查数据库里面很多条数据,这种情况下,
把查的代码做成迭代器后,就会节约内存空间,不用一次性把数据全部加载到内存中去,
迭代一次,从数据库查一点,加载到内存中
.
.
生成器,迭代器器,可迭代对象详解
# 创建一个迭代器类,并用该类实例化生成迭代器对象
from builtins import StopIteration
class Teng(object):
def __init__(self):
self.counter = 0
def __iter__(self):
return self
def __next__(self):
self.counter += 1
if self.counter == 5:
raise StopIteration()
return self.counter
obj2 = Teng()
res1 = obj2.__next__()
res2 = obj2.__next__()
res3 = obj2.__next__()
res4 = obj2.__next__()
res5 = obj2.__next__() # 走到这步会报错,因为主动抛异常了
print(res1, res2, res3, res4, res5)
obj1 = Teng()
for i in obj1:
print(i) # 可以把迭代器对象里面的值一个一个的取出来,一直取完
# for 循环的底层会自动捕获异常
# 迭代器对象支持通过__next__方法取值,如果取值结束,则自动抛出异常
# for循环底层是先用迭代器对象先点__iter__方法,获取返回值迭代器对象本身,
# 然后再通过对象点__next__方法,并将返回值赋值给变量i
# 对象点__next__方法,运行过程中出现异常,会被捕获,并结束迭代器对象点__next__方法的取值
# 所以我们用到的for循环,它的内部实际上是基于迭代器的,基于迭代器的__next__方法,
# 来实现迭代取值的
-----------------------------------------
-----------------------------------------
# 创建一个生成器对象
def func():
yield 1
yield 2
# 创建生成器对象,(内部是根据解释器自带的生成器类,generator类创建的对象),生成器类的内部也声明了:__iter__,__next__方法)
obj1 = func() # 得到生成器对象,该对象中也有__iter__,__next__方法
res1 = obj1.__next__()
res2 = obj1.__next__()
res3 = obj1.__next__() # 取不到值的时候,会自动报错
obj2 = func()
for i in obj2:
print(i) # for循环取不到,报错被捕获,并结束for循环
# 所以根据迭代器的定义,生成器也是一种特殊的迭代器
-----------------------------------------
-----------------------------------------
# 可迭代对象官方定义:
# 如果一个类里面定义了__iter__方法,并返回了一个迭代器对象
# 则我们称这个类创建的对象为可迭代对象!!!
def func():
yield 1
yield 2
yield 3
obj1 = func()
class Foo(object):
def __iter__(self):
# return 迭代器对象
return obj1
obj2 = Foo() # 就生成了可迭代对象了!
# 如果一个对象是可迭代对象,那么该对象将可以被for循环,因为for循环底层是先把对象点__iter__方法,拿到返回值(迭代器对象),
# 然后通过返回值去点__next__方法,去迭代取值
for i in obj2:
print(i)
# 迭代器对象与可迭代对象,都可以被for循环,迭代取值!!!
.
.
python的内置函数 range就是一个可迭代对象
------------------------------------
res = range(10) # 可迭代对象
print(dir(res)) # 能看到里面有__iter__方法,但没有__next__方法
res1 = res.__iter__() # 迭代器对象
print(dir(res1)) # 能看到里面有__iter__方法,与__next__方法
------------------------------------
class IterRange(object):
def __init__(self, max_num):
self.max_num = max_num
self.counter = -1
def __iter__(self):
return self
def __next__(self):
self.counter += 1
if self.counter == self.num:
raise StopIteration()
return self.counter
------------------------------
# 配合可迭代类,与迭代器对象,构建自定义Xrange方法,Xrange就是一个可迭代对象
class Xrange(object):
def __init__(self, max_num):
self.max_num = max_num
def __iter__(self):
return IterRange(self.max_num)
obj = Xrange(100)
for i in obj:
print(i)
--------------------------------
# 还可以配合可迭代类,与生成器对象,构建自定义Xrange方法,Xrange就是一个可迭代对象
class Xrange(object):
def __init__(self,max_num):
self.max_num = max_num
def __iter__(self):
counter = 0
while counter < self.max_num:
yield counter
counter +=1
obj = Xrange(100)
for i in obj:
print(i)
---------------------------------------
.
.
.
python中 列表与字典 都是可迭代对象
from collections.abc import Iterator, Iterable
# Iterable判断对象是不是可迭代对象 Iterator判断对象是不是迭代器对象
v1 = [11, 22, 33]
print(isinstance(v1, Iterator)) # False
v2 = v1.__iter__()
print(isinstance(v2, Iterator)) # True
print(isinstance(v1, Iterable)) # True
print(isinstance(v2, Iterable)) # True
.
.
.
.
Django ORM中的N+1问题
# 假设有以下模型
class Book(model.Model):
"""书籍"""
models.ForeignKey(Author) # 这里忽略了其他参数,请注意
pass
class Author(model.Model):
"""作者"""
models.ForeignKey(Country)
pass
class Country(model.Model):
"""城市"""
pass
--------------------------------------
# 假设有如下查询代码,假设book表里面有100条数据
books = Book.objects.order_by("title")
for book in books:
print(book.title, "by", book.author.name)
# 那么通过for循环得到的N个对象,每一个对象点author外键,都会执行一次sql
# 所以最后执行了N+1次的sql语句
# 如果 n 累计下来,这里就会产生严重的性能问题
--------------------------------------
如果我们再获取作者的城市信息
books = Book.objects.order_by("title")
for book in books:
print(
book.title,
"by",
book.author.name,
"from",
book.author.country.name,
)
# 循环第三步每次循环将增加对 country 表的查询,所以现在的总查询次数是 2n+1
--------------------------------------
--------------------------------------
# 如何解决 N+1 问题
django 给我们提供了两个将多次查询集成为一个复杂查询的方法,select_related 和 prefetch_related
# select_related 底层基于连表的查询
# prefetch_related 底层基于子查询
# 如果正常用表模型对象点关联表里面的字段时,肯定会走sql查询,但是用了select_related后
# 底层是先连表后查询,再封装到书籍对象里面去
books = Book.objects.order_by("title").select_related("author")
for book in books:
print(book.title, "by", book.author.name)
# 先将书表和作者表拼到一起,然后把大表里面的对应的数据封装到一个个书籍对象里面去
# 这样再用对象去点外键表里面的字段的时候就不会再走sql查询了
# 所以用了select_related后,最后总共执行了1次sql,而不是 N+1次了
# 所以用了prefetch_related后,最后总共执行了2次sql,而不是 N+1次了
--------------------------------------
.
.
.
.
.
web服务器的介绍与作用
# 实现WSGI协议的web服务器:
uWSGI: 用c语言写的,性能比较高
wsgiref: 性能较低,用于开发环境
werkzeug: 符合wsgi协议的web服务器+工具包(封装了一些东西),flask使用werkzeug
gunicorn: python写的
tornado: tornado包含了框架和服务器
---------------------------------------------------
# web服务器实现了什么?
将客户端发送的HTTP请求,转化到python变量。
将python变量中的数据,转化为HTTP响应。
---------------------------------------------------
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)