python学习第八周总结
多进程实现TCP服务端并发
之前我们学习了用socket模块进行文字以及文件的传输,但是之前的操作一个服务端只能与一个客户端进行交互,如果要想实现多个客户端与服务端交互的并发效果,我们需要进行如下操作,将接收发送分别封装成一个函数,每次进行一次连接就相当于多了一个进程:
服务端:
import socket
from multiprocessing import Process
def get_sever():
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
return server
def get_talk(sock):
while True:
data = sock.recv(1024)
print(data.decode('utf8'))
sock.send(data.upper())
if __name__ == '__main__':
server = get_sever()
while True:
sock, addr = server.accept()
p = Process(target=get_talk, args=(sock,))
p.start()
'''每新增一个客户端就会新增一个人进程,相当于雇了服务员,每新增一个客户端就会新雇一名服务员'''
客户端:
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8080))
while True:
client.send(b'hello world')
data = client.recv(1024)
print(data)
互斥锁代码实操
我们之前尝试的抢票代码中,所有的用户都可以抢票成功,这是因为在多个进程执行的同一刻,时间足够短所以多个进程可以同时改文件内的数据。for循环可以看做同一时间执行,图中执行效果并不是按照顺序来执行。要想避免此操作我们需要使用互斥锁。互斥锁建议加在数据处理的部分,因为互斥锁会拖慢代码执行的效率,只加在购票过程中,如果查票也要加锁name查票也需要排队,不符合逻辑。
import random
import json
import time
from multiprocessing import Process,Lock
def search(name):
with open(r'data.json', 'r', encoding='utf8') as f:
ticket_dict = json.load(f)
if ticket_dict.get('ticket_num') > 0:
print(f'尊敬的{name},目前还有余票{ticket_dict.get("ticket_num")}张')
else:
print('票已售罄')
def buy(name):
with open(r'data.json', 'r',encoding='utf8') as f:
ticket_dict = json.load(f)
time.sleep(random.randint(1,3))
if ticket_dict.get('ticket_num') > 0:
with open(r'data.json', 'w', encoding='utf8') as f:
ticket_dict['ticket_num'] -= 1
json.dump(ticket_dict, f)
print(f'尊敬的{name},您成功购买一张车票')
else:
print('余票不足,暂时无法购买')
def run(name, mutex):
search(name)
mutex.acquire()
buy(name)
mutex.release()
if __name__ == '__main__':
mutex = Lock()
for i in range(10):
p = Process(target=run, args=('用户%s' % i, mutex))
p.start()
线程理论
1.进程:是资源单位,表示一块内存空间。
2.线程:是执行单位,表示真正的代码指令。
"""
每个进程内都至少有一个线程,线程才是真正跟CPU打交道的角色。如果把进程比作一个车间,那么车间内的生产线就是线程。
有了进城为什么还要有线程呢?
1.进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
2.进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
"""
3.线程特性:
3.1一个进程号可以设置多个线程
3.2同一个进程下的多个线程是数据共享的
3.3创建进程和线程的区别:创建进程的资源远大于线程
创建线程的两种方式
方法1:
from threading import Thread
import time
def task(name):
print(f'{name} is running')
time.sleep(3)
print(f'{name} is over')
t = Thread(target=task, args=('max',))
t.start() # 创建一个线程
print('主线程')
'''
max is running主线程
max is over
'''
"""
创建进程时由于子进程加载需要时间,所以会先执行子进程下面的操作。而创建线程消耗资源极小,所以会先打印max is running
"""
方法2:s
class MyThread(Thread):
def run(self):
print('run is running')
time.sleep(1)
print('run is over')
obj = MyThread()
obj.start()
print('主线程')
创建进程CPU资源远大于线程
线程的诸多特性
1.join方法:
"""
正常情况下我们建立线程,会先执行子线程代码(IO状态除外),再执行主线程代码
"""
from threading import Thread
import time
def task(name):
print(f'{name} is running')
time.sleep(1)
print(f'{name} is over')
t = Thread(target=task, args=('max',))
t.start()
print('主线程')
'''
max is running主线程
max is over
'''
"""
加上join之后会先执行子线程代码,再执行主线程代码(和进程一样)
"""
from threading import Thread
import time
def task(name):
print(f'{name} is running')
time.sleep(1)
print(f'{name} is over')
t = Thread(target=task, args=('max',))
t.start()
t.join()
print('主线程')
'''
max is running
max is over
主线程
'''
2.同一个进程下的多个线程数据共享
from threading import Thread
money = 1000
def task():
global money
money = 666
t = Thread(target=task)
t.start()
t.join()
print(money)
'''
666 改变的是该线程名称空间内的money,子线程和主线程数据共享
'''
3.current_thread():查看是主线程还是子线程,主线程是MainThread,子线程是Thread-数字,数字从1开始排
from threading import Thread, current_thread
money = 1000
def task():
global money
money = 666
print(current_thread().name)
t = Thread(target=task)
t.start()
t.join()
print(money)
print(current_thread().name)
'''
Thread-1
666
MainThread
'''
3.线程也可以用os.getpid()查看进程号,它们都同属一个进程,所以进程号一致
from threading import Thread
import os
money = 1000
def task():
global money
money = 666
print('子线程进程号>>>:',os.getpid())
t = Thread(target=task)
t.start()
t.join()
print('主线程进程号>>>:',os.getpid())
'''
子线程进程号>>>: 34388
主线程进程号>>>: 34388
'''
4.active_count:用来查看存活的线程数量
"""
由于每次创建线程保留速度很短,线程创建之后就会消失,可以采用sleep的方法查看效果
"""
from threading import Thread, active_count
import os
import time
money = 1000
def task():
print('子线程进程号>>>:',os.getpid())
time.sleep(3)
for i in range(10):
t = Thread(target=task)
t.start()
print('存活的线程数:', active_count())
'''
11
'''
5.守护线程:守护线程和守护进程一样,会等待主进程(或线程)运行完毕后被销毁
from threading import Thread
import time
def task():
print('子线程运行task开始')
time.sleep(3)
print('子线程运行task结束')
t = Thread(target=task)
t.daemon = True
t.start()
print('主线程')
'''子线程运行task开始主线程''' # '子线程运行task结束'由于主线程运行结束没有执行
GIL全局解释器锁
"""
GIL是CPython解释器的特性,和Python语言无关系
"""
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.
翻译:
1.在CPython解释器中存在全局解释器锁简称GIL
python解释器有很多类型
CPython JPython PyPython (常用的是CPython解释器)
2.GIL本质也是一把互斥锁,用来阻止同一个进程内多个线程同时执行(重要)
'''利用多道技术,同一时间同一个进程内,只能利用一个CPU'''
3.GIL的存在是因为CPython解释器中内存管理不是线程安全的(垃圾回收机制)
垃圾回收机制:引用计数、标记清除、分代回收
'''每一个进程都有一个垃圾回收机制的线程。为了防止每个数据值建立时就被垃圾回收机制回收,CPython中规定多个线程不能同时执行'''
4.所有解释型语言都逃不开上述的问题。
GIL与普通互斥锁
既然CPython解释器中有GIL 那么我们以后写代码是不是就不需要操作锁了!!!
"""
GIL只能够确保同进程内多线程数据不会被垃圾回收机制弄乱
并不能确保程序里面的数据是否安全
"""
import time
from threading import Thread,Lock
num = 100
def task(mutex):
global num
mutex.acquire()
count = num
time.sleep(0.1)
num = count - 1
mutex.release()
mutex = Lock()
t_list = []
for i in range(100):
t = Thread(target=task,args=(mutex,))
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(num)
python多线程是否有用
需要分情况
情况1
单个CPU
多个CPU
情况2
IO密集型(代码有IO操作)
计算密集型(代码没有IO)
1.单个CPU
IO密集型
多进程
申请额外的空间 消耗更多的资源
多线程
消耗资源相对较少 通过多道技术
ps:多线程有优势!!!
计算密集型
多进程
申请额外的空间 消耗更多的资源(总耗时+申请空间+拷贝代码+切换)
多线程
消耗资源相对较少 通过多道技术(总耗时+切换)
ps:多线程有优势!!!
2.多个CPU
IO密集型
多进程
总耗时(单个进程的耗时+IO+申请空间+拷贝代码)
多线程
总耗时(单个进程的耗时+IO)
ps:多线程有优势!!!
计算密集型
多进程
总耗时(单个进程的耗时)
多线程
总耗时(多个进程的综合)
ps:多进程完胜!!!
from threading import Thread
from multiprocessing import Process
import os
import time
def work():
# 计算密集型
res = 1
for i in range(1, 100000):
res *= i
if __name__ == '__main__':
# print(os.cpu_count()) # 12 查看当前计算机CPU个数
start_time = time.time()
# p_list = []
# for i in range(12): # 一次性创建12个进程
# p = Process(target=work)
# p.start()
# p_list.append(p)
# for p in p_list: # 确保所有的进程全部运行完毕
# p.join()
t_list = []
for i in range(12):
t = Thread(target=work)
t.start()
t_list.append(t)
for t in t_list:
t.join()
print('总耗时:%s' % (time.time() - start_time)) # 获取总的耗时
"""
计算密集型
多进程:5.665567398071289
多线程:30.233906745910645
"""
def work():
time.sleep(2) # 模拟纯IO操作
if __name__ == '__main__':
start_time = time.time()
# t_list = []
# for i in range(100):
# t = Thread(target=work)
# t.start()
# for t in t_list:
# t.join()
p_list = []
for i in range(100):
p = Process(target=work)
p.start()
for p in p_list:
p.join()
print('总耗时:%s' % (time.time() - start_time))
"""
IO密集型
多线程:0.0149583816528320
多进程:0.6402878761291504
"""
死锁现象
acquire()
release()
from threading import Thread,Lock
import time
mutexA = Lock() # 产生一把锁
mutexB = Lock() # 产生一把锁
class MyThread(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutexA.acquire()
print(f'{self.name}抢到了A锁')
mutexB.acquire()
print(f'{self.name}抢到了B锁')
mutexB.release()
print(f'{self.name}释放了B锁')
mutexA.release()
print(f'{self.name}释放了A锁')
def func2(self):
mutexB.acquire()
print(f'{self.name}抢到了B锁')
time.sleep(1)
mutexA.acquire()
print(f'{self.name}抢到了A锁')
mutexA.release()
print(f'{self.name}释放了A锁')
mutexB.release()
print(f'{self.name}释放了B锁')
for i in range(10):
obj = MyThread()
obj.start()
死锁现象
acquire()
release()
from threading import Thread,Lock
import time
mutexA = Lock() # 产生一把锁
mutexB = Lock() # 产生一把锁
class MyThread(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutexA.acquire()
print(f'{self.name}抢到了A锁')
mutexB.acquire()
print(f'{self.name}抢到了B锁')
mutexB.release()
print(f'{self.name}释放了B锁')
mutexA.release()
print(f'{self.name}释放了A锁')
def func2(self):
mutexB.acquire()
print(f'{self.name}抢到了B锁')
time.sleep(1)
mutexA.acquire()
print(f'{self.name}抢到了A锁')
mutexA.release()
print(f'{self.name}释放了A锁')
mutexB.release()
print(f'{self.name}释放了B锁')
for i in range(10):
obj = MyThread()
obj.start()
信号量
在python并发编程中信号量相当于多把互斥锁(公共厕所)
from threading import Thread, Lock, Semaphore
import time
import random
sp = Semaphore(5) # 一次性产生五把锁
class MyThread(Thread):
def run(self):
sp.acquire()
print(self.name)
time.sleep(random.randint(1, 3))
sp.release()
for i in range(20):
t = MyThread()
t.start()
进程池与线程池
进程和线程能否无限制的创建 不可以
因为硬件的发展赶不上软件 有物理极限 如果我们在编写代码的过程中无限制的创建进程或者线程可能会导致计算机奔溃
池
降低程序的执行效率 但是保证了计算机硬件的安全
进程池
提前创建好固定数量的进程供后续程序的调用 超出则等待
线程池
提前创建好固定数量的线程供后续程序的调用 超出则等待
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import os
import time
import random
from threading import current_thread
# 1.产生含有固定数量线程的线程池
# pool = ThreadPoolExecutor(10)
pool = ProcessPoolExecutor(5)
def task(n):
print('task is running')
# time.sleep(random.randint(1, 3))
# print('task is over', n, current_thread().name)
# print('task is over', os.getpid())
return '我是task函数的返回值'
def func(*args, **kwargs):
print('from func')
if __name__ == '__main__':
# 2.将任务提交给线程池即可
for i in range(20):
# res = pool.submit(task, 123) # 朝线程池提交任务
# print(res.result()) # 不能直接获取
# pool.submit(task, 123).add_done_callback(func)
协程
"""
进程:资源单位
线程:执行单位
协程:单线程下实现并发(效率极高)
在代码层面欺骗CPU 让CPU觉得我们的代码里面没有IO操作
实际上IO操作被我们自己写的代码检测 一旦有 立刻让代码执行别的
(该技术完全是程序员自己弄出来的 名字也是程序员自己起的)
核心:自己写代码完成切换+保存状态
"""
import time
from gevent import monkey;
monkey.patch_all() # 固定编写 用于检测所有的IO操作(猴子补丁)
from gevent import spawn
def func1():
print('func1 running')
time.sleep(3)
print('func1 over')
def func2():
print('func2 running')
time.sleep(5)
print('func2 over')
if __name__ == '__main__':
start_time = time.time()
# func1()
# func2()
s1 = spawn(func1) # 检测代码 一旦有IO自动切换(执行没有io的操作 变向的等待io结束)
s2 = spawn(func2)
s1.join()
s2.join()
print(time.time() - start_time) # 8.01237154006958 协程 5.015487432479858
存取数据的演变史
1.文本文件:
文件路径不固定(导致代码兼容性下降)
数据格式不统一(max|123,max_123)
2.软件开发目录:
1.规定了数据应该保存在db目录下>>>:路径偏向统一
2.数据格式没有得到统一:文本、json格式、对象等多种形式
3.数据库服务:
1.统一了路径
2.统一了操作方式
3.降低学习成本,提高开发效率
数据库软件应用史
1.单机游戏:数据存储于各个计算机本地,无法共享
2.网络游戏:数据存储于网络中,可以共享
"""
数据库服务集群:提升数控的安全性
"""
数据库的本质
1.站在底层原理的角度,数据库指的是操作数据的进程(一堆代码)
2.站在实际应用的角度,数据库指的是可视化操作界面(一些软件)
ps:以后不做特殊说明的情况下讲数据库其实指的是数据库软件
"""
数据库软件本质也是CS架构的程序,意味着所有的程序员都有资格编写出一款数据软件
"""
数据库的分类
1.关系型数据库:
1.1特征:
1拥有固定的表结构,类似于wps表格,表头要固定,每个字段都有固定的字段类型(id,name.gender)。
2.数据之间可以建立关系(通过身份证号可以查到籍贯)
1.2软件代表:
MySQL:开源免费,使用最广,性价比最高。
oracle:收费,使用成本高但是安全性也高,主要用于银行等机构,但由于成本高昂正在被MySQL取代。
postgreSQL:开源免费,支持二次开发,兼容性极高。
MariaDB:根MySQL是一个作者,开源免费
sqlite:小型数据库,主要用于本地测试。
2.非关系型数据库:
2.1特征:
1.没有固定的表结构,数据采用K:V键值对的形式。
2.数据之间无法建立数据库层面的关系,需要自己编写代码建立逻辑层面的关系。
"""数据库层面的关系:比如删除了用户姓名,用户的房产信息等就会被自动的删除。代码层面的关系是指删除了用户姓名,房产等信息需要用户自己手动删除"""
2.2软件代表:
redis:目前最火,使用频率最高的非关系型数据库(缓存数据库),虽然缓存数据库是基于内存做数据处理但是拥有持久化的功能(有日志记录,可以永久保存)。
'''缓存数据库:数据库存取在内存中速度速度很快,大型软件一般用关系型数据库来做永久存储,非关系型数据库来做数据查询'''
mongoDB:文档型数据库,最像关系型数据库的非关系型数据库,主要用在爬虫以及大数据领域。
mecache:已经被redis淘汰。
MySQL简介
1.版本问题:
8.0:最新版
5.7:使用频率较高
5.6:学习推荐使用
ps:站在开发的角度使用哪个版本学习都没有关系
2.下载流程:
1.访问官网:https://www.mysql.com/
2.点击DOWNLOADS并点击GPL
3.点击community server
4.点击Archives(作用是查看以前版本,旧版本很多东西需要人为处理,有利于学习阶段使用,所以采用旧版本)
5.选择对应系统的对应版本下载即可(zip压缩包)
3.主要目录介绍:
bin目录:存放启动文件:mysqld.exe(服务端)、mysql.exe(客户端)
data:存储数据
my-default.ini:默认配置文件
readme:软件说明
系统服务制作
"""
通常我们打开musql客户端之前需要先打开服务端,较为麻烦,我们可以进行系统服务制作,将mysql服务端设置成计算机的守护进程,苹果系统出厂时已进行如下设置,无需重复设置。
"""
1.将mysql的bin目录添加到系统环境变量中(添加成功之后需要重启cmd窗口)
2.将mysql添加到系统服务中,方法如下:
如何查看系统服务:1.进入任务管理器.点击服务;2.在cmd窗口输入setvices.msc回车
1.以管理员身份运行终端,输入mysql --install 回车,此时mysql就已经添加到系统服务当中
2.查看报系统服务,可以看到mysql已经在系统服务当中但是还没运行。可以直接在服务当中右键>>>启动;或者直接在终端管理员窗口输入net start mysql,此后可以不再执行该操作。
"""
如果想重启服务端,如下操作应在终端>>>以管理员身份运行 中进行:
1.先关闭客户端:net stop mysql
2.移除系统服务:net start mysql
"""
SQL与NoSQL
数据库的服务端可以服务多种类型的客户端,客户端可以是自己开发的,也可以是python代码编写,也可以是java代码编写。
SQL:操作关系型数据库的语言。
NoSQL:操作非关系型数据库语言。
"""
想要跟数据库交互,就必须使用数据库指定的语言。SQL可能指的是语言,也可能指的是SQL数据库。NoSQL值得可能是语言,也可能是非关系型数据库。
"""
数据库的概念
"""
小白在该阶段为了更形象,可以做如下比喻,但是比喻和实际情况还是有点区别
"""
库:相当于是文件
表:相当于是目录里面的文件
记录:相当于是文件里面的一行行数据
基本操作:
1.查看所有库的名称:show databases;(显示的结果和mysql中data目录下的文件夹的名字对得上,这就是所有库的集合)。
2.查看所有表名称:show tables;
3.切换库:use 库名
4.查看所有记录:select * from musql.user/G;
基本SQL语句
1.使用SQL语句需要注意以下几点:
使用SQL语句必须以分号结尾
sql语句编写错误不用担心可以直接执行报错,或者是使用\c,该行就不会执行
2.基于库的增删改查:
2.1创建库:create database 库名
2.2查看库:查看全部库:show databases;查看指定库信息:show create database 库名
2.3编辑库:alter database 库名charset='utf8'
2.4删除库:drop database 库名
3.基于表的增删改查:
查看库下所有表名称:show tables;
创建表:create table 表名(字段名 字段类型)
查勘表信息:describe 表名;简写:desc 表名
'''如果想要查看其他库中的表,只需要在表名前加上库名.就可以'''
表名重命名:alter table 原表名 rename 新表名
drop table 表名
基于记录的基本SQL语句
1.插入数据:insert into 表名values(数据值)
2.查看数据:select * from 表名
3.查看表指定字段数据:select 字段名 from 表名
4.编辑数据:update 表名 set 字段名=指定数据 where 字段名=指定数据
5.删除数据:delete from 表名 where 字段=指定字符
字符编码配置文件
1.\s:查看当前MySQL相关信息:用户、版本、编码、端口号。查找过程中我们发现客户、服务等字符编码都是不一样的,我们需要将它们都改成相同的编码。
2.修改步骤:(1).新建一个配置文件改名为my.ini;(2).拷贝以下代码到my.ini文件中:
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_general_ci
[client]
default-character-set=utf8mb4
[mysql]
default-character-set=utf8mb4
"""
PS:
1.utf8mb4能够存储表情,比utf8功能更强大
2.utf8与utf-8是有区别的,MySQL中只有utf
3.修改了配置文件中关于[mysqld]的配置,需要重启服务端
"""
数据库存储引擎
1.存储引擎:数据库针对数据采用多种存取方式。
2.查看常见存储引擎的方式:show engines;
3.需要掌握的四个存储引擎:
1.MyISAM:MySQL5.5之前的默认存储引擎,特点:存储速度快,但是功能较少,安全性较差。
2.InnoDB:MySQL5.5之后的存储引擎,支持事务,外键,行锁等操作,存取速度没有MyISAM快,但是安全性更高。
3.Memory:基于内存存取数据,仅用于临时表存取数据。
4.BlackHole:任何写入的数据都会立刻丢失。
创建表的完整语法
1.语法:create table 表名(
字段名 字段类型(数字) 约束条件,
字段名 字段类型(数字) 约束条件,
字段名 字段类型(数字) 约束条件
)
"""
1.字段名和字段类型必须要有
2.数字和约束条件可以没有
3.约束条件也可以是多个,空格隔开即可
4.最后一行末尾不能加逗号
"""
字段类型之整形
mysql有以下几种整形:
1.tinyint:大小:1bytes(8个比特位,也就是8位0或1,可以表示256个数字),有正负号,有256种结果,unsigned情况下包含0,不包含256
2.smallint:大小:2bytes(16个比特位,也就是16位0或1,可以表示65536个数字),有正负号,有65536种结果,unsigned情况下包含0,不包含65536
3.int,integer:大小:4bytes(32个比特位,也就是32位0或1,可以表示4294967296个数字,无法表示电话号码),有正负号,unsigned情况下包含0,不包含4294967296
4.bigint:大小:8bytes(66位比特位,也就是64位0或1,可以表示18446744073709551616个数字,可以表示电话号码),有正负号,unsigned情况下包含0,不包含18446744073709551616
'''如果想只存储0和整数,需要在整形后面加上unsigned'''
严格模式
我们在插入数字时,插入范围外的数字应该直接报错,而不是添加一个范围末端的数字,针对上述需求,我们对mysql5.6版本做以下操作:
1.首先输入show variables like mode '%mode%';查看sql_mode,发现里面暂未添加任何东西。
2.修改配置文件,在my.ini中的服务端[mysqld]中加入:sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES。再重启mysqld服务端(net stop mysql; net start mysql),发现我们再插入数字超出范围的时候会直接报错,这正是我们想要的效果。
字段类型之浮点型
字段类型浮点型有三个关键字:float(20,10),double(20,10),decimal(20,10);三个关键字后面括号内的数字含义都相同:总计30位数字,小数点前面10位,小数点后面4位。其中三种浮点型精度:decimal>double>float。
字段类型之字符类型
1.char(4):最长4个字符,过长就报错,不够4位用空格填充至4位。
2.varchar(4):最长4个字符,过长就报错,不够4位是几位就是几位。
3.char与varchar对比:
char
优势:整存整取、速度快()
劣势:浪费存储空间
'''如果添加字符长度不等的数据,底层会自动将数据补全至定长。取的时候也是按定长来取,存取方便'''
varchar
优势:节省存储空间
劣势:存取数据的速度较char慢
'''存的时候会自动保留原来的字符长度,节省空间,但是取出的时候不好取,因为字符长度都不一致。取出的时候可以在每个名字前面加上报头,通过把报头解析出每个名字的长度。所以varchar存取数据的速度较char慢'''
char与varchar的使用需要结合具体应用场景
数字的含义
数字在很多地方用来限制字符的长度,但在整型当中不是。在整型当中如果我们的数字需要凑够固定位数,位数不够的在前面补0,我们可以在int(或tinyint、bigint等)后面的括号内加上一个数字,并且在字段类型的后面加上zerofill,不够固定位数的数字就会被不全称为固定位数。
字段类型i枚举与集合
生活中很多场景需要让用户来选择,在mysql语法中我们需要在用户添加表数据时限定一个范围,如果用户上传的数据超出范围会直接报错。单选称为枚举,多选称为集合(集合也可以单选)。
语法结构举例说明:
1.枚举:
create table t1(
id int unsigned,
name varchar(16),
gender enum('male','female')
);
2.集合:
create table t2:
id int unsigned,
name varchar(16),
hobbies set('soccer','basketball','run')
);
create table t4(
id int unsigned,
register_time datetime,
birthday date,
study time,
work_year year
);
字段类型之日期类型
字段类型:
datetime:年月日时分秒
date:年月日
time:时分秒
year:年
create table t4(
id int unsigned,
register_time datetime,
birthday date,
study time,
work_year year
);
insert into t4 values(1,'2019-7-15 20:28:28','1997/2/6','11:11:11','2009');
无符号、零填充
1.unsigned:整型可以带符号,在字段类型后面加上unsigned时默认只取正数和0。
2.zerofill:加在整形数据类型后面,加上默认会自动填充0至整形括号内的位数,如果整形后面没有括号,默认填充为11位。无符号和零填充可以共同使用。
create table t7(
id int(5) unsigned zerofill
);
非空
1.我们在创建表的过程中可以只给部分的字段名传值,语法结构为:insert into 表名(字段名) values(数据值)。剩余不传值的字段名默认为NULL,类似python中的None。
2.如果表中有数据必须要上传,可以在生成表的过程中在字段类型后面加上not null,加上该约束条件之后该字段名不传值会报错。
create table t9(
id int unsigned not null,
name varchar(16)
);
insert into t9 values(1,'max');
insert into t9(name) values('jerry'); # 报错
默认值
1.在生成表格的过程中如果某个字段的结果大致确定,我们可以采用默认值,类似函数中的默认参数。用户如果不上传该参数,结果以默认为准。语法结构:在字段类型后面加上 default 默认值 即可。
create table t10(
id int unsigned,
name varchar(4) default '匿名'
);
insert into t10(id) values(1);
唯一值
1.单列为一:在创建数据的时候我们需要有的数据能够保持唯一,例如身份证号。只需要在字段类型后面加上unique即可。
2.联合唯一:创建数据时有几个条件不能同时相同,比如身份证号后四位和出生年月不能同时相同,几个条件组合在一起不能重复我们称之为联合唯一。需要在表中最后一行加入unique(),括号内加上联合唯一的字段名。
create table t11(
id int unsigned unique,
end_num varchar(4),
birthday date,
unique(end_num,birthday)
);
insert into t11 values(1,'0014','1997-02-06');
insert into t11 values(1,'jack','1997-03-24'); # 报错 原因:单列唯一
insert t11 values(2,'0014','1997-02-06'); # 报错 原因:联合唯一
主键
1.主键的语法结构为:primary key,跟在字段类型后面,相当于not null + unique(非空且唯一)。
2.主键是组织数据的重要条件,并且可以加快数据的查找速度,InnoDB存储引擎规定了所有的表必须有且只有一个主键。
2.1当表中没有主键也没有,也没有其他非空且唯一的字段名,InnoDB会采用一个隐藏的主键,隐藏意味着无法使用,基于该表的数据只能一行行查询,速度很慢。
2.2当表中没有主键但是有其他非空且唯一的字段,那么会从上往下将第一个非空且唯一的字段自动升级成主键。
"""
我们在创建表的时候应该有一个字段用来标识数据的唯一性,并且该字段通常情况下就是id字段,我们也可以根据项目具体情况命名成为'pid','sid'等。
"""
自增
1.当我们使用id或其他字段来给数据编号时,每次都要上传id,并且如果间隔时间较长,我们,嗯可能会忘记这次的id是多少。所以我们采用自增的方法,该方法类似于excel中的编号。语法结构加在主键后面:auto_increment。每次上传的时候不用上传被自增修饰的字段。
create table t13(
id int unsigned primary key auto_increment,
name varchar(16)
);
insert into t13(name) values('max');
insert into t13(name) values('jerry');
"""
自增的缺陷:自增不会因为数据删除而自动回退,永远会增长,如果想要重置某张表的主键值,需要使用truncate 表名来清空数据并重置主键。
"""
外键
1.我们在制作一张员工表时,一班表上体现的信息需要有:
id name age dep_name(部门) dep_desc
但是我们在使用这张表的过程中会发现以下问题:
1.语义不明确,不知道主体是员工还是部门。
2.因为部门比较有限,新增大量员工时会重复输入部门,浪费内存空间,造成空间冗余。
3.如果员工绑定的部门需要更换名字或其他属性时,工作量很大并且很麻烦。
2.鉴于上述缺点,我们将表一分为二,分成员工表(id name age),和部门表(id dep_name dep_desc)。上述问题得到解决,但是员工和部门没有了关系,因此我们需要在员工表里增加一个字段dep_id,将部门的id填入员工表中的dep_id字段,就时的不同表之间产生了联系。因此得出概念外键字段:用于表示数据与数据之间关系的字段。dep_id就是外键字段。
"""
外键字段:用于表示数据与数据之间关系的字段。
"""
关系的判断
1.关系有多种:一对一,一对多,多对多。
2.关系的判断可以用'换位思考原则'。
一对多关系
以员工和部门的关系举例,站在员工的角度,一个员工只能有一个部门,而一个部门可以拥有多个员工。该种关系称为一对多的关系,针对一对多关系,外键字段放在'多的一方'。
"""
针对代码编写有以下需要注意的点:
1.创建表的时候应该先创建被关联表
2.录入表数据时应先录入被关联表
"""
create table emp(
id int unsigned primary key auto_increment,
name varchar(16),
age int unsigned,
dep_id int unsigned,
foreign key(dep_id) references dep(id)
on update cascade
on delete cascade
);
create table dep(
id int unsigned primary key auto_increment,
name varchar(16)
);
"""
上述我们发现部门数据删除之后,和删除部门绑定的员工数据也删除了,所以外键还是有一定缺陷。外键其实是强耦合,不符合解耦合的特性,所以很多时候,实际项目中当表较多的情况,我们可能不会使用外键 而是使用代码建立逻辑层面的关系。
"""
多对多关系
刚才研究了一对多的关系,但是日常生活中也有很多多对多的关系,例如书籍合作者,一本书的作者可以是好几位作家,一位作家也可以撰写多本书籍。所以书籍表和作者表是多对多的关系。
create table book(
id int unsigned primary key auto_increment,
title varchar(16),
price float(5,2)
);
create table auther(
id int unsigned primary key auto_increment,
name varchar(16),
phone bigint unsigned
);
create table book2auther(
id int unsigned primary key auto_increment,
auther_id int unsigned,
foreign key(auther_id) references auther(id)
on update cascade
on delete cascade,
book_id int unsigned,
foreign key(book_id) references book(id)
on update cascade
on delete cascade
);
insert into book(title,price) values('book1','22.22');
insert into book(title,price) values('book2',54.67);
insert into auther(name,phone) values('auther1',2345);
insert into auther(name,phone) values('auther2',3456);
insert into book2auther(auther_id,book_id) values(1,1),(1,2),(2,1),(2,2);
一对一关系
一对一的关系是双方都只能仅拥有对方。针对一对一的关系,外键字段放在任意一方都可以,但推荐放在查询频率较高的一方。
create table course_list(
id int unsigned primary key auto_increment,
name varchar(32)
);
create table class(
id int unsigned primary key auto_increment,
name varchar(32),
course_list_id int unsigned,
foreign key(course_list_id) references course_list(id)
on update cascade
on delete cascade
);
insert into course_list(name) values('课程表1');
insert into course_list(name) values('课程表2');
insert into class(name,course_list_id) values('班级一',1);
insert into class(name,course_list_id) values('班级一',2);
SQL语句查询
1.select:指定需要查找的字段信息:eg:select *,select name,age,同时select也支持对字段做处理,eg:select char_length(name)。
2.from:指定需要查询的表信息,from mysql.user,from 表名。
3.SQL语句中关键字的执行顺序和编写顺序并不是一致的,例如:select id,name from userinfo;我们先写的select在写的from,但是执行的时候是先执行的from在执行select,对应关键字的编写顺序和执行顺序我们没必要过多的在意,我们只需要把注意力放在每个关键字的功能上即可。
前期数据准备
我们用以下表以及数据来引出今天的知识点。
create table emp(
id int primary key auto_increment,
name varchar(20) not null,
gender enum('male','female') not null default 'male', #大部分是男的
age int(3) unsigned not null default 28,
hire_date date not null,
post varchar(50),
post_comment varchar(100),
salary double(15,2),
office int, #一个部门一个屋子
depart_id int
);
#插入记录
#三个部门:教学,销售,运营
insert into emp(name,gender,age,hire_date,post,salary,office,depart_id) values
('jason','male',18,'20170301','浦东第一帅形象代言',7300.33,401,1), #以下是教学部
('tom','male',78,'20150302','teacher',1000000.31,401,1),
('kevin','male',81,'20130305','teacher',8300,401,1),
('tony','male',73,'20140701','teacher',3500,401,1),
('owen','male',28,'20121101','teacher',2100,401,1),
('jack','female',18,'20110211','teacher',9000,401,1),
('jenny','male',18,'19000301','teacher',30000,401,1),
('sank','male',48,'20101111','teacher',10000,401,1),
('哈哈','female',48,'20150311','sale',3000.13,402,2),#以下是销售部门
('呵呵','female',38,'20101101','sale',2000.35,402,2),
('西西','female',18,'20110312','sale',1000.37,402,2),
('乐乐','female',18,'20160513','sale',3000.29,402,2),
('拉拉','female',28,'20170127','sale',4000.33,402,2),
('僧龙','male',28,'20160311','operation',10000.13,403,3), #以下是运营部门
('程咬金','male',18,'19970312','operation',20000,403,3),
('程咬银','female',18,'20130311','operation',19000,403,3),
('程咬铜','male',18,'20150411','operation',18000,403,3),
('程咬铁','female',18,'20140512','operation',17000,403,3);
"""
针对select后面的字段名可以先用*占位往后写,最后再回来修改,在实际应用中select后面很少直接写*,因为*表示所有,当表中字段和数据都特别多的情况下非常浪费数据库资源。
"""
查询关键字之where筛选
1.查询范围:查询id大于等于3小于等于6的数据
'''SQL支持逻辑运算符'''
select * from emp where id>=3 and id<=6;
select * from emp where id between 3 and 6;
2.查询薪资是20000或者18000或者17000的数据
select * from emp where salary=17000 or salary=18000 or salary=20000;
select * from emp where salary in (20000,18000,17000);
查询关键字之group by分组
1.分组:按照指定的条件,将单个单个的数据组成一个个整体,比如将班级学生按照性别分组,将全国人民按照人民分组。
2.聚合函数,专门用于分组之后的数据统计,max(最大值),min(最小值),sum(求和),avg(求平均值),count(计数)。
3.举例:将员工数据按照部门分组
select * from emp group by post;
"""
不同版本MySQLl针对上句话作出不同处理:MySQL5.6不会报错,5.7及8.0会报错,因为5.7以上版本认为最小单位就是组,而不应该再试组内单个数据单个字段,group by后面的字段名也应该写在select后面,eg:select post from emp group by post。
如果我们相对5.6最初改变实现上述效果,我们需要做以下操作:在my.ini中客户端添加以下内容:only_full_group_by。添加成功后可以看到效果会直接报错。
"""
4.获取每个部门的最高工资
select post,max(salary) from emp group by post;
5.统计每个部门的部门名称以及部门下的员工姓名
select post,group_concat(name) from emp group by post;
"""
当分组时其他的字段我们没法直接用到,要么使用聚合函数来使用其它的字段,要么是用group_concat(其他字段名)来使用,同时我们也可以在字段之间添加一些特殊符号来分割数据
"""
select post,group_concat(name,'|',age) from emp group by post;
查询关键字之having过滤
having与where本质是一样的,都是用来对数据做筛选,只不过where用在分组之前(首次筛选),having用在分组之后(二次筛选)。
统计各部门年龄在30岁以上的员工平均工资,并且保留大于10000的数据。
select post,avg(salary) from emp where age > 30 group by post having avg(salary) > 10000;
查询关键字之distinct去重
表中有很多字段下有重复的数据,或者是几个字段组合起来有重复的数据,我们如果想要去重需要用到distinct关键字,distinct后面如果跟一个字段名就是一个字段名下的数据去重,跟多个字段名就是组合去重。
select distinct age from emp;
select distinct age,gender from emp;
查询关键字order by排序
表中如果有数据需要我们进行排序时,可以用order by字段名来排序,order by后面的字段名就是默认排序的依据,如果想要按照降序排列需要在后面加上desc(升序后面后缀为asc,默认省略)
1.单个字段排序:
select * from emp order by age; # 升序
select * from emp order by age desc; # 降序
2.多个字段排序:先按照前面的字段名排序,如果相同在按照后面的字段名排序
select * from emp order by age,salary;
select * from emp order by hire_date,age; # hire_date,age都按照升序
select * from emp order by age,salary desc; # 年龄按照升序,薪资按照降序
select * from emp order by age desc,salary desc; # 年龄、薪资都按照降序
3.统计各部门年龄在10岁以上的员工平均工资,并且保留平均工资大于1000的部门,然后对平均工资进行排序
select post,avg(salary) from emp where age > 10 group by post having avg(salary) > 1000 order by avg(salary);
"""
约束条件顺序可简单的记为:where,group,having,order
"""
查询关键字之limit分页
当网站数据较多时,我们无法一次全部呈现,我们需要将数据分页展示。我们能看到的大多数网站都做了分页处理,每页都只能存放固定数量的数据。下面用例子说明limit的用法。
select * from emp limit 5; limit后面跟一个参数时,表示一次性展示5条数据。
select * from emp limit 5,3; limit后面跟两个特参数表示从第6条开始展示,展示3条,也就是6、7、8条。
查询关键字之regexp正则表达式
SQL语句的模糊匹配如果用不习惯 也可以自己写正则批量查询
select * from emp where name regexp '^j.*(n|y)$';
多表查询的思路
表数据准备
create table dep(
id int primary key auto_increment,
name varchar(20)
);
create table emp(
id int primary key auto_increment,
name varchar(20),
sex enum('male','female') not null default 'male',
age int,
dep_id int
);
#插入数据
insert into dep values
(200,'技术'),
(201,'人力资源'),
(202,'销售'),
(203,'运营'),
(205,'财务');
insert into emp(name,sex,age,dep_id) values
('jason','male',18,200),
('dragon','female',48,201),
('kevin','male',18,201),
('nick','male',28,202),
('owen','male',18,203),
('jerry','female',18,204);
上述两个表格分别如下,我们的初衷是把它们合在一起,让第一个表格的dep_id和第二个表格的部门名称name对应,但是却出现了每一个dep_id分别匹配了所有id的效果,涉及到两张表对应时,字段很容易冲突,针对上述情况我们称之为'笛卡尔积'。解决笛卡尔积的办法是我们在字段前面加上表名来指定。
select * from emp,dep where emp.dep_id=dep.id;
emp,dep的顺序不能颠倒,如果dep在前就是查看字段dep并且把emp中匹配的数据匹配进去
作业
1. 查询岗位名以及岗位包含的所有员工名字
select post,group_concat(name) from emp group by post;
2. 查询岗位名以及各岗位内包含的员工个数
select post,count(name) from emp group by post;
3. 查询公司内男员工和女员工的个数
select gender,count(gender) from emp group by gender;
4. 查询岗位名以及各岗位的平均薪资
select post,avg(salary) from emp group by post;
5. 查询岗位名以及各岗位的最高薪资
select post,max(salary) from emp group by post;
6. 查询岗位名以及各岗位的最低薪资
select post,min(salary) from emp group by post;
7. 查询男员工与男员工的平均薪资,女员工与女员工的平均薪资
select gender,avg(salary) from emp group by gender;
8. 统计各部门年龄在30岁以上的员工平均工资
select post,avg(salary) from emp where age > 30 group by post;
python学习第八周总结的更多相关文章
- python学习第八讲,python中的数据类型,列表,元祖,字典,之字典使用与介绍
目录 python学习第八讲,python中的数据类型,列表,元祖,字典,之字典使用与介绍.md 一丶字典 1.字典的定义 2.字典的使用. 3.字典的常用方法. python学习第八讲,python ...
- 201671010140. 2016-2017-2 《Java程序设计》java学习第八周
第八周Java学习 本周,老师带领我们完善了一下继承,借口,拷贝,lambda表达式,内部类方面欠缺,不完善的地方,帮助我们查漏补缺. 以拷贝的学习为例,我本来对拷贝的理解非常浅 ...
- python学习笔记第二周
目录 一.基础概念 1.模块 1)os模块 2)sys模块 2.pyc文件 3.数据类型 1)数字 2)布尔值 3)字符串 4.数据运算 5.运算符 6.赋值运算 7.逻辑运算 8.成员运算 9.身份 ...
- Python学习笔记八
类的高级用法 多态: 在其他语言,使用的是类的继承. 在python中,不需要指定数据类型. 基于TCP协议的socket通信实现: 类似于打电话的情景. 服务端: 1.买手机 2.插卡 3.开机 ...
- python学习(八)阶段性总结
这里学习了python的一点点基础然后来总结一下 python是一个强类型的语言 数字: 数字的类型大概有:整数.浮点数.复数.固定精度的十进制数.带分子和分母的有理数 数字与字符串之间不能直接相加, ...
- python 学习笔记八 进程和线程 (进阶篇)
什么是线程(thread)? 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执 ...
- python学习笔记八--动态类型
一.变量,对象,引用: 1. 变量: 2. 对象:均包含了一个头部信息,有以下两部分内容 a.对象的数据类型, b.引用计数器:纪录当前引用货指向该对象的数量,一旦计数器被清零,该对象的内存空间就会被 ...
- python学习笔记(八)、特殊方法、特性和迭代器
1 新式类和旧式类 python类的工作方式在不断变化.较新的Python2版本有两种类,其中旧式类正快速退出舞台.新式类时Python2.2 引入的,提供了一些额外功能,如支持函数super 和 p ...
- Python学习(八) —— 内置函数和匿名函数
一.递归函数 定义:在一个函数里调用这个函数本身 递归的最大深度:997 def func(n): print(n) n += 1 func(n) func(1) 测试递归最大深度 import sy ...
- Python 学习 第八篇:函数2(参数、lamdba和函数属性)
函数的参数是参数暴露给外部的接口,向函数传递参数,可以控制函数的流程,函数可以0个.1个或多个参数:在Python中向函数传参,使用的是赋值方式. 一,传递参数 参数是通过赋值来传递的,传递参数的特点 ...
随机推荐
- 【题解】CF991C Candies
题面传送门 解决思路 看到 \(10^{18}\) 的范围,我们可以想到二分答案.只要对于每一个二分出的答案进行 \(check\) ,如果可行就往比它小的半边找,不可行就往比它大的半边找. 以下是 ...
- vcenter密码设置永不过期
由于机房断电,原本的vcenter重启后web页面出现报错,为尽快恢复vcenter管理机,直接停用了旧的vcenter虚机,重新安装了一台vcenter,两个月后,页面开始报警密码即将到期: 经查阅 ...
- 基于.NetCore开发博客项目 StarBlog - (20) 图片显示优化
前言 我的服务器带宽比较高,博客部署在上面访问的时候几乎没感觉有加载延迟,就没做图片这块的优化,不过最近有小伙伴说博客的图片加载比较慢,那就来把图片优化完善一下吧~ 目前有两个地方需要完善 图片瀑布流 ...
- 树莓派蓝牙rfcomm协议通信
修改配置文件 手机使用 "蓝牙串口" 软件,树莓派上修改文件/etc/systemd/system/dbus-org.bluez.service ExecStart=/usr/li ...
- python3实现AES加密
前言 这几天研究了一下 python 实现 AES 加密,有很多坑 AES 加密的参数及其条件 这个 AES 加密的主要坑就在于这些条件, 首先 aes 加密有一下几个参数 秘钥:加密的时候用秘钥,解 ...
- day09 常用工具类&包装类&集合——List、Set
day09 常用工具类 java.lang.Math数值运算 基本数值运算,如初等函数.对数.平方根和三角函数 //最大最小值 Math.max(12, 21); Math.min(2, 3); // ...
- Linux 基础-新手必备命令
Linux 基础-新手必备命令 概述 常见执行 Linux 命令的格式是这样的: 命令名称 [命令参数] [命令对象] 注意,命令名称.命令参数.命令对象之间请用空格键分隔. 命令对象一般是指要处理的 ...
- 常用BOM操作 DOM操作 事件 jQuery类库
目录 BOM操作 常用BOM操作 三种弹出框 alert confirm prompt 定时任务 setTimeout 循环定时 setInterval DOM操作 查找标签 直接查找 间接查找 操作 ...
- Python报AttributeError: module 'string' has no attribute 'join'解决方法
报:AttributeError: module 'string' has no attribute 'join' 属性错误:模块"string"没有属性"join&qu ...
- Qt大型工程开发技术选型Part3:Qt调用C#编写的COM组件实例
Qt大型工程开发技术选型Part3:Qt调用C#编写的COM组件实例以及错误总结 ok,前面铺垫了那么多,现在来写一个开发实例,我会把其中隐藏的坑和陷阱简单谈谈,并在文章最后总结. 不愿意看长篇大论的 ...