第九周学习笔记总结

总结

操作系统的发展史

发展史 作用
穿孔卡片 优势:一个人独占电脑
劣势:CPU利用率极低
联机批处理系统 缩短录入数据的时间让CPU连续工作的时间变长,提升CPU利用率
脱机批处理系统 是现代计算机的雏形,提升CPU利用率

总结:操作系统的发展史也可以看成是cpu利用率提升的发展史

进程

多道技术

多道技术的出现就是为了提升CPU的利用率,降低程序的等待时间。目前我们研究并发都是以计算机是单核(一个CPU)的情况下

计算机利用空闲时间提前准备好一些数据 提高效率 总耗时较短

空间上的复用:多个任务共用一套计算机硬件
时间上的复用:切换+保存状态
CPU在两种情况下会切换(去执行其他程序):
    1.程序遇到IO操作,CPU自动切走运行其他程序
    2.程序长时间占用CPU,系统发现之后也会强行切走CPU,保证其他程序也可以使用
保存状态
    每次切换之前要记录下当前执行的状态 之后切回来基于当前状态继续执行

进程理论

程序和进程的概念

程序:一堆躺在文件上的死代码

进程:正在被运行的程序(活的)

进程的调度算法

1.先来的先开始服务

2.短作业优先

3.时间片轮转法+多级反馈队列

将固定的时间均分成很多份,所有的程序来了都公平的分一份
分配多次之后如果还有程序需要运行,则将其分到下一层
越往下表示程序总耗时越长,每次分的时间片越多,但是优先级越低

进程的并行与并发

并发

多个进程看上去像同时执行就可以称之为并发
原理:让CPU在多个程序之间利用多道技术来回切换+保存状态.
'单个CPU完全可以实现并发的效果,如果是并行那么肯定也属于并发''

并行

多个进程同时执行
'单个CPU肯定无法实现并行 必须要有多个CPU'

进程的三状态

1.阻塞态:程序执行过程中有IO操作

2.运行态:程序被CPU执行着

3.就绪态:程序之进入运行态之前肯定要处于就绪态

1.所有的进程要想被运行 必须先经过就绪态
2.运行过程中如果出现了IO操作 则进入阻塞态
3.运行过程中如果时间片用完 则继续进入就绪态
4.阻塞态要想进入运行态必须先经过就绪态

同步异步与阻塞非阻塞

同步和异步用于描述任务的提交状态

同步:提交完任务之后原地等待任务的结果,期间不做任何事

异步:提交完任务之后不原地等待直接去做其他事,结果自动提醒

阻塞与非阻塞用于描述进程的执行状态

阻塞:就是阻塞态

非阻塞:就是运行态和就绪态

同步阻塞:在银行排队 并且在队伍中什么事情都不做
同步非阻塞:在银行排队 并且在队伍中做点其他事
异步阻塞:取号 在旁边座位上等着叫号 期间不做事
异步非阻塞:取号 在旁边座位上等着叫号 期间为所欲为

想要提升程序执行的效率,就要想办法让我们的程序一直处于就绪态和运行态

创建进程的多种方式

创建进程的方式有两种:

第一种是双击桌面的程序图标

第二种是代码创建进程

创建进程的本质:在内存中申请一块内存空间用于运行相应的程序代码。一个进程对应在内存中就是一块独立的内存空间,多个进程对应在内存中就是多块独立的内存空间,进程与进程之间数据默认情况下是无法直接交互,如果想交互可以借助于第三方工具、模块。以下是代码创建进程的二种方式。

第一种方式

from multiprocessing import Process
import time

def task(name):
    print(f'{name}正在运行')
    time.sleep(3)
    print(f'{name}运行结束')


if __name__ == '__main__':
    p = Process(target=task, args=('jason',))  # 创建一个进程对象
    p.start()  # 告诉操作系统创建一个进程(异步操作)
    # task('jason')  # 普通的函数调用是同步操作
    print('主进程')

第二种方式

from multiprocessing import Process
import time

class MyProcess(Process):
    def __init__(self, name):
        super().__init__()
        self.name = name

    def run(self):
        print(f'{self.name}正在运行')
        time.sleep(5)
        print(f'{self.name}运行结束')


if __name__ == '__main__':
    obj = MyProcess('jason')
    obj.start()
    print('主进程')

进程间数据默认隔离

多个进程数据彼此之间默认是相互隔离的
  如果真的想交互 需要借助于'管道'或者'队列'

from multiprocessing import Process

money = 100

def task():
    global money  # 局部修改全局不可变类型
    money = 666
    print('子进程打印的money', money) # 666


if __name__ == '__main__':
    p = Process(target=task)
    p.start()
    p.join()  # 确保子进程代码运行结束再打印money
    print('父进程打印的money', money) # 100

进程间通信(IPC机制)

什么是队列:先进先出

from multiprocessing import Queue

1.创建队列对象
q = Queue(3)  # 括号内指定队列可以容纳的数据个数 默认:2147483647
2.往队列添加数据
q.put(111)
print(q.full())  # 判断队列是否已经存满
q.put(222)
q.put(333)
print(q.full())
q.put(444)  # 超出数据存放极限 那么程序一致处于阻塞态 直到队列中有数据被取出
3.从队列中取数据
print(q.get_nowait())
print(q.get_nowait())
print(q.get_nowait())  # 队列中如果没有数据可取 直接报错
print(q.get())
print(q.empty())  # 判断队列是否已经空了
print(q.get())
print(q.get())
print(q.empty())
print(q.get())  # 超出数据获取极限 那么程序一致处于阻塞态 直到队列中有数据被添加
"""
    q.full()
    q.empty()
    q.get_nowait()
上述方法在多进程下不能准确使用(失效)!!!
"""

IPC机制

1.主进程与子进程通信

2.子进程与子进程通信

from multiprocessing import Queue, Process


def procedure(q):
    q.put('子进程procedure往队里中添加了数据')


def consumer(q):
    print('子进程的consumer从队列中获取数据', q.get())


if __name__ == '__main__':
    q = Queue()  # 在主进程中产生q对象 确保所有的子进程使用的是相同的q
    p1 = Process(target=procedure, args=(q,))
    p2 = Process(target=consumer, args=(q,))
    p1.start()
    p2.start()
    print('主进程')

生产者消费模型

完整的生产者消费者模型至少有三个部分 作用
生产者 产生数据
消息队列/数据库 将产生的数据进行储存
消费者 处理数据

进程相关方法

主进程等待子进程运行结束之后再运行

join()

python查看进程号的方法

1.current_process函数
    from multiprocessing import Process, current_process
    current_process().pid
    获取进程号的用处之一就是可以通过代码的方式管理进程
    windows      taskkill关键字  结束进程
    mac/linux    kill关键字      结束进程
2.os模块
    os.getpid()     获取当前进程的进程号
    os.getppid()    获取当前进程的父进程号

销毁子进程

terminate()

判断进程是否存活

is_alive()

守护进程

如何理解守护?

伴随着守护对象的存活而存活 死亡而死亡

from multiprocessing import Process
import time


def task(name):
    print('大内总管:%s存活' % name)
    time.sleep(3)
    print('大内总管:%s嗝屁' % name)


if __name__ == '__main__':
    p = Process(target=task, args=('基佬',))
    p.daemon = True  # 将子进程设置为守护进程:主进程代码结束 子进程立刻结束
    p.start()
    # p.daemon = True  # 必须在start之前执行
    print('天子Jason寿终正寝!!!')

僵尸进程与孤儿进程

僵尸进程

进程已经运行结束,但是相关的资源并没有完全清空,需要父进程参与回收

孤儿进程

父进程意外死亡,子进程正常运行,该子进程就称之为孤儿进程

孤儿进程也不是没有人管,操作系统会自动分配福利院接收

互斥锁

互斥锁:将并发变成串行,虽然牺牲了程序的执行效率但是保证了数据安全

from multiprocessing import Process, Lock
mutex = Lock()
mutex.acquire()  # 抢锁
mutex.release()  # 释放锁

"""
多个程序同时操作一份数据的时候很容易产生数据错乱!!!
为了避免数据错乱 我们需要使用互斥锁
"""

线程

线程理论

分类
进程 资源单位 相当于是车间,进程负责给内部的线程提供相应的资源
线程 执行单位 相当于是车间里面的流水线,线程负责执行真正的功能
1.一个进程至少含有一个线程
2.同一个进程下多个线程之间资源共享
分类 区别
多进程 需要申请内存空间,需要拷贝全部代码 资源消耗大
多线程 不需要申请内存空间,也不需要拷贝全部代码,资源消耗小

创建线程的两种方式

from threading import Thread
import time

# 第一种
def task(name):
    print(f'{name} is running')
    time.sleep(3)
    print(f'{name} is over')

"""
开设线程不需要完整拷贝代码 所以无论什么系统都不会出现反复操作的情况 
也不需要在启动脚本中执行 但是为了兼容性和统一性 习惯在启动脚本中编写
"""

# 创建线程无需在__main__下面编写 但是为了统一 还是习惯在子代码中写
t = Thread(target=task, args=('jason', ))
t.start()  # 创建线程的开销极小 几乎是一瞬间就可以创建
print('主线程')
if __name__ == '__main__':
    t = Thread(target=task, args=('jason',))
    t.start()
    print('主线程')

# 第二种
class MyThread(Thread):
    def __init__(self, name):
        super().__init__()
        self.name = name

    def run(self):
        print(f'{self.name}正在运行')
        time.sleep(3)
        print(f'{self.name}运行结束')


obj = MyThread('jason')
obj.start()
print('主线程')

多线程实现TCP服务端并发

比多进程更加简单方便 消耗的资源更少

# 服务端
import socket
from threading import Thread

server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen()

def talk(sock):
    while True:
        data = sock.recv(1024)
        print(data.decode('utf8'))
        sock.send(data.upper())

while True:
    sock, addr = server.accept()
    # 每类一个客户端就创建一个线程做数据交互
    t = Thread(target=talk, args=(sock,))
    t.start()

# 客户端
import socket

client = socket.socket()
client.connect(('127.0.0.1', 8080))

while True:
    client.send(b'hello baby')
    data = client.recv(1024)
    print(data.decode('utf8'))

线程对象相关方法

1.进程号
    同一个进程下开设的多个线程拥有相同的进程号
2.线程名
    from threading import Thread, current_thread
    current_thread().name
    主:MainThread    子:Thread-N
3.进程下的线程数
    active_count()
4.主线程等到子线程运行结束之后再运行
    join()

同一个进程下线程间数据共享

守护线程

守护线程伴随着被守护的线程的结束而结束

from threading import Thread
import time

def task():
    print('子线程运行task函数')
    time.sleep(3)
    print('子线程运行task结束')

t = Thread(target=task)
# t.daemon = True
t.start()
# t.daemon = True
print('主线程')
"""
进程下所有的非守护线程结束 主线程(主进程)才能真正结束!!!
"""

GIL全局解释器锁

1.GIL的研究是Cpython解释器的特点 不是python语言的特点
2.GIL本质也是一把互斥锁
3.GIL的存在使得同一个进程下的多个线程无法同时执行(关键)
	言外之意:单进程下的多线程无法利用多核优势 效率低!!!
4.GIL的存在主要是因为cpython解释器中垃圾回收机制不是线程安全的

理解:

GIL的存在是必要的,如果不存在会产生垃圾回收机制与正常线程之间数据错乱,GIL是加在CPython解释器上面的互斥锁。同一个进程下的多个线程要想执行必须先抢GIL锁,所以同一个进程下多个线程肯定不能同时运行,也就是无法利用多核优势。

结论:

1.python的多线程确实无法使用多核优势 但是在IO密集型的任务下是有用的

2.GIL只确保解释器层面数据不会错乱(垃圾回收机制),针对程序中自己的数据应该自己加锁处理

3.所有的解释型编程语言都没办法做到同一个进程下多个线程同时执行

验证GIL的存在和特点

验证GIL的存在

from threading import Thread

money = 100

def task():
    global money
    money -= 1

t_list = []
for i in range(100):
    t = Thread(target=task)
    t.start()
    t_list.append(t)  # [线程1 线程2 线程3 ... 线程100]
for t in t_list:
    t.join()
# 等待所有的线程运行结束 查看money是多少
print(money)

验证GIL的特点

"""GIL不会影响程序层面的数据也不会保证它的修改是安全的要想保证得自己加锁"""
from threading import Thread,Lock
import time

money = 100
mutex = Lock()

def task():
    mutex.acquire()
    global money
    tmp = money
    time.sleep(0.1)
    money = tmp - 1
    mutex.release()

t_list = []
for i in range(100):
    t = Thread(target=task)
    t.start()
    t_list.append(t)
for t in t_list:
    t.join()
# 为了确保结构正确 应该等待所有的线程运行完毕再打印money 
print(money)

验证python多线程是否有用

单个CPU IO密集型(代码有IO操作) 计算密集型(代码没有IO操作)
多进程 申请额外的空间 消耗更多的资源 申请额外的空间 消耗更多的资源(总耗时+申请空间+拷贝代码+切换)
多线程 消耗资源相对较少 通过多道技术 消耗资源相对较少 通过多道技术(总耗时+切换)
优势 多线程有优势 多线程有优势
多个CPU
多进程 总耗时(单个进程的耗时+IO+申请空间+拷贝代码) 总耗时(单个进程的耗时)
多线程 总耗时(单个进程的耗时+IO) 总耗时(多个进程的综合)
优势 多线程有优势 多进程完胜

死锁现象

1.什么是死锁?

如果多个进程里面的每个进程都在等待其他一个进程(包括自身)才能继续往下执行,若无外力他们将无法推进,这种情况就是死锁,处于死锁状态的进程称为死锁进程。

2.死锁产生的原因?

因竞争资源发生死锁现象:系统中供多个进程共享的资源的数目不足以满足全部进程的需要时,就会引起对诸资源的竞争而发生死锁现象

信号量

信号量本质也是互斥锁,只不过它是多把锁。信号量在不同的知识体系中,展示出来的功能是不一样的。在并发编程中信号量意思是多把互斥锁。在django框架中信号量意思是达到某个条件自动触发特定功能。

event事件

子进程\子线程之间可以彼此等待彼此
    eg:
      子A运行到某一个代码位置后发信号告诉子B开始运行

from threading import Thread, Event
import time

event = Event()  # 类似于造了一个红绿灯

def light():
    print('红灯亮着的 所有人都不能动')
    time.sleep(3)
    print('绿灯亮了 油门踩到底 给我冲!!!')
    event.set()

def car(name):
    print('%s正在等红灯' % name)
    event.wait()
    print('%s加油门 飙车了' % name)

t = Thread(target=light)
t.start()
for i in range(20):
    t = Thread(target=car, args=('熊猫PRO%s' % i,))
    t.start()
# 这种效果其实也可以通过其他手段实现 比如队列(只不过没有event简便)

进程池和线程池

名称 作用
降低程序的执行效率 保证计算机硬件的安全
进程池 提前创建好固定个数的进程供程序使用,后续不会再创建
线程池 提前创建好固定个数的线程供程序使用,后续不会再创建
"""
多进程 多线程
    在实际应用中是不是可以无限制的开进程和线程
    肯定不可以!!! 会造成内存溢出受限于硬件水平
我们在开设多进程或者多线程的时候 还需要考虑硬件的承受范围
"""

submit(函数名,实参1,实参2,...)

from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
from threading import current_thread
import os
import time

# pool = ThreadPoolExecutor(5)  # 固定产生五个线程
pool = ProcessPoolExecutor(5)  # 固定产生五个线程

def task(n):
    # print(current_thread().name)
    print(os.getpid())
    # print(n)
    time.sleep(1)
    return '返回的结果'

def func(*args, **kwargs):
    print('func', args, kwargs)
    print(args[0].result())

if __name__ == '__main__':
    for i in range(20):
        # res = pool.submit(task,123)  # 朝池子中提交任务(异步)
        # print(res.result())  # 同步
        # pool.submit(task, 123).add_done_callback(func)
        """异步回调:异步任务执行完成后有结果就会自动触发该机制"""
        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

协程实现TCP服务端并发

# 服务端
import socket
from gevent import monkey;monkey.patch_all()  # 固定编写 用于检测所有的IO操作(猴子补丁)
from gevent import spawn


def communication(sock):
    while True:
        data = sock.recv(1024)  # IO操作
        print(data.decode('utf8'))
        sock.send(data.upper())


def get_server():
    server = socket.socket()
    server.bind(('127.0.0.1', 8080))
    server.listen(5)
    while True:
        sock, addr = server.accept()  # IO操作
        spawn(communication, sock)

s1 = spawn(get_server)
s1.join()

# 客户端
from threading import Thread, current_thread
import socket

def get_client():
    client = socket.socket()
    client.connect(('127.0.0.1', 8080))
    count = 0
    while True:
        msg = '%s say hello %s'%(current_thread().name, count)
        count += 1
        client.send(msg.encode('utf8'))
        data = client.recv(1024)
        print(data.decode('utf8'))

# 创建诸多线程
for i in range(200):
    t = Thread(target=get_client)
    t.start() 

总结:

python可以通过开设多进程,在多进程下开设多线程,在多线程使用协程,从而让程序执行的效率达到极致。因为大部分程序都是IO密集型的,所以实际业务中很少需要如此之高的效率(一直占着CPU不放)。

数据库

数据存取演变史

1.文本文件

2.软件开发目录规范

3.数据库服务

数据库软件应用史

1.单机游戏

不同计算机上的相同程序,数据无法共享,数据库服务全部在本地完成

2.网络游戏

不同计算机上的相同程序,数据可以共享,数据库服务单独在网络架设(远程数据库服务)

数据库集群:1.数据安全性问题 2.服务器负载问题
让多台服务器运行相同的数据库服务

数据库的本质

1.数据库站在底层原理的角度:数据库指的是专门用于操作数据的进程。

eg:运行在内存中的代码

2.数据库站在现实应用的角度:数据库指的是拥有操作界面的应用程序。

eg:用于操作进程的界面

我们平时在说数据库的时候大部分指的是操作数据库的应用软件

数据库软件本质是一款cs架构的应用程序

言外之意所有的程序员理论上都可以编写!!!>>>:市面上已经有很多数据库软件

数据库的分类

关系型数据库

1.数据的组织方式有明确的表结构(最主要的特征)

2.表与表之间可以建立数据库层面的关系

常见关系型数据库名称

名称 说明
MySQL 开源免费,使用最广泛,数据库学习必学
postgreSQL 开源免费,支持二次开发
MariaDB 开源免费,与MySQL是同一个作者,用法也极其相似
Oracle 收费,安全性极高,但是使用成本高,主要用于银行及各大重要机关
sqlite 小型数据库,主要用于本地测试(django框架自带该数据库)
db2 IBM开发的一种大型关系型数据库平台
sql server 老牌数据库软件,目前主流不用
非关系型数据库

1.数据的组织方式没有明确的表结构,是以k:v键值对的形式组织的(最主要特征)

2.数据之间无法直接建立数据库层面的关系

名称 说明
redis 目前最火,使用频率最高的缓存型数据库
mongoDB 稳定型数据库,最像关系型的非关系型,主要用于爬虫,大数据
memcache 已经被redis淘汰
SQL与NoSQL

SQL语句的意思是操作关系型数据库的语法

NoSQL语句的意思是操作非关系型数据库的语法

ps:SQL有时候也用来表示关系型数据库,NoSQL也用来表示非关系型数据库

MySQL数据库

MySQL简介

版本问题

5.6X:前几年使用频率最高的版本
5.7X:最近尝试迁移的版本(频率增加)
8.0X:最新版,功能很强大,但是线上环境几乎不用(本地自己用非常好用)
'''虽然版本有区别,但是操作上几乎没有区别,主要体现在底层运作'''

下载

官网 https://www.mysql.com/

1.下载步骤:

1.访问官网>>>2.点击DOWNLOADS>>>3.点击MySQL Community(GPL) Downloads>>>4.点击MySQL Community Server>>>5.点击archives>>>6.选择版本和系统>>>7.点击download

讲解以windows为基准,mac和linux参考下列博客.
linux:https://www.cnblogs.com/Dominic-Ji/p/15124625.html  
mac:https://www.cnblogs.com/Dominic-Ji/articles/15402755.html

2.解压处理

建议压缩到D或者E、F盘的根目录方便查找。mysql-5.6.44-winx64.zip >>> mysql-5.6.44-winx64。

目录结构

bin文件夹
    mysqld.exe  服务端
    mysql.exe  客户端
'''学习阶段服务端在本地启动即可'''
data文件夹
  存放数据
my-default.ini文件
  默认配置文件
README文件
    说明书
基本使用

1.先启动服务端

可能会报错:拷贝关键信息去百度

2.查找mysql文件位置

输入mysqld,启动服务端
cmd窗口就是服务器,不要关闭

3.再次开启新的cmd窗口

输入mysql,充当客户端
直接回车会以游客模式进入,功能很少

4.用户名密码登录

mysql -u用户名 -p密码
  mysql默认管理员账号 用户名是root 密码是空

5.退出

exit
quit
系统服务制作

我们可以直接添加mysql的bin目录到环境变量中,就可以直接输入mysql等命令。

将mysql服务端制作成系统服务(随着计算机的开启而启动 关闭而结束)

1.以管理员身份打开cmd窗口
2.执行系统服务命令
  mysqld --install
3.启动服务端
  1.右键直接点击启动
  2.命令启动
    net start mysql
命令 说明
services.msc 查看系统服务的命令
net start mysql 启动mysql服务端
net stop mysql 关闭mysql服务端
mysqld --remove 移除系统服务(先确保服务已经关闭)
密码相关操作

直接输入mysql登入的是默认的游客模式,没有太多的操作权限,我们可以使用用户名和密码的方式登录。

修改密码

通用方式:在cmd窗口下直接修改(不要登录进去)

mysqladmin命令
mysqladmin -u用户名 -p原密码 password 新密码
第一次修改
  MySQLadmin -uroot -p password 123
第二次修改
  mysqladmin -uroot -p123 password 321

偏门方式:登录状态下修改当前用户密码(先登录)

set password=PASSWORD('新密码');
数据库重要概念
名称 相当于
文件夹
文件夹里面的文件
记录 文件里面一行行数据

查看库,表,记录的命令

命令 说明
show databases; 查看所有的数据库
show tables; 查看所有的表
select * from mysql.user; 查看user表里面所有的记录

补充

符号 作用
; SQL语句结束符
\c 取消SQL语句的执行

基本SQL语句

针对库的基本SQL语句
语句和类型 说明
create database 库名; 创建一个库
show databases; 查看所有库的名称
show create database 库名; 指定查看某个库的信息
alter database 库名 charset='gbk'; 修改字符编码
drop database 库名; 删除库
针对表的基本SQL语句
查看当前所在的库名: select database(); 如果没有切换指定的库 那么默认是NULL

切换或进入库:use 库名;
语句和类型 说明
create table 表名(字段名1 字段类型1,字段名2 字段类型2); 创建一个表,表里含有字段1和2
show tables; 查看当前库下所有的表的名称
show create table 表名; 指定查看某个表的信息
describe 表名; 指定查看表的字段信息
desc 表名; describe 表名;的简写
alter table 旧表名 rename 新表名; 修改表名
drop table 表名; 删除表
针对记录的基本SQL语句

前提:要想操作记录,那么肯定得先有库和表。

语句和类型 说明
insert into 表名 values(数据,数据); 增加单条数据
insert into 表名 values(数据,数据),(数据,数据); 增加多条数据
select * from 表名; 查看表里所有的数据
select 字段1,字段2 from 表名; 查看表里指定的字段
update 表名 set 字段名=新数据 where 筛选条件; 根据条件筛选数据并修改
delete from 表名; 删除表里所有数据
delete from 表名 where 筛选条件; 根据条件删除数据

ps:如果查看表的时候表中字段较多出现了错乱,可以结尾写\G。

posted @   空白o  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
点击右上角即可分享
微信分享提示