zoukankan      html  css  js  c++  java
  • Python9 数据库进阶

    Python9 ---- 数据库进阶

    数据库进阶

    tip: 通过 desc 表名命令查看当前表信息

    tip2: as 关键字可以为我们的字段或者表取别名

    Mysql约束

    顾名思义就是对表中的数据进行限定, 目的是保证数据的正确性, 有效性和完整性

    • 主键约束primary key

      • 什么是主键
        关系表中记录的唯一标识(不能为null, 不可重复)

        选取和业务无关的字段, 常用的主键就是自增id

        数据库引擎会通过主键建立索引, 索引是一个有序结构, 我们希望当前主键的值不要改变, 避免重新建立索引, 影响性能.

      • 添加主键

        • 建表时添加

          CREATE TABLE store_perf(
          id int PRIMARY KEY auto_increment,
          store_name varchar(255)
          )
        • 通过可视化界面操作(建议)

      • 主键的长度限制
        767bytes, 对应的长度为767/4=191

        bytes表示字节, 一个utf8mb4按四个字节算, 所以长度最大为191

      • 自增键必须为主键

        Incorrect table definition; there can be only one auto column and it must be defined as a key
      • 联合主键
        主键其实只能有一个, 如果在图形界面当中选择多个字段作为主键, 那么程序会自动帮我们创建联合主键

      • 修改主键

        • 添加主键
          ALTER TABLE store_perf ADD PRIMARY KEY (`id`)
        • 移除主键
          ALTER TABLE store_perf DROP PRIMARY KEY
        • 添加联合主键
          ALTER TABLE store_perf ADD PRIMARY KEY (`id`, `store_name`)
    • 非空约束(空白和null是两个概念)
      NOT NULL

      只能约束程序层面上没有操作该表中的某个字段, 不能约束人行为上对其赋值为空白字符.

    • 唯一约束
      UNIQUE

      NULL可以重复

      比如统计店铺业绩, 该店铺不可以重复, 那就可以使用唯一约束

    • 外键约束foreign key

      • 外键的作用
        保持数据的一致性和完整性, 通过外键来表达主表和从表的关系, 避免冗余字段.

      • 为什么现在不用外键了?

        • 性能问题
          插数据需要校验

        • 并发问题
          在高并发的事务场景下, 使用外键容易造成死锁.

        • 扩展性问题

          • 外键就相当于把对象之间的关系交给数据库来维护, 如果遇到分库分表, 外键是不生效的.
          • 作数据迁移时, 触发器, 存储过程和外键都很难迁移, 增加了维护成本.
        • 维护成本

          • 需要有专业DBA来维护庞大的数据库关系
    • default
      默认约束, 默认会填充当前字段.

      如果我们没有给一个有默认约束的字段create_time值, 那么该字段会默认填充CURRENT_TIMESTAMP

    分组查询GROUP BY

    将数据按某个字段进行分组, 配合SUM, AVG, COUNT, MAX, MIN等聚合函数做统计使用

    INSERT INTO store_perf(store_name, amount, department) values ("男装_店铺1", 34543123.23, "男装事业部"), ("男装_店铺2", 41232373.12, "男装事业部"),("男装_店铺3", 62135632.22, "男装事业部"), ("男装_店铺4", 612356123.26, "男装事业部"), ("女装_店铺1", 78716237854.3, "女装事业部"), ("女装_店铺2", 89123565741.23, "女装事业部"), ("女装_店铺3", 831235769.23, "女装事业部"), ("女装_店铺4", 1912312345.13, "女装事业部"), ("女装_店铺5", 87324234712.92, "女装事业部"), ("女装_店铺6", 12754547457.93, "女装事业部")
    SELECT count(*) as 店铺数, 
    FORMAT(AVG(amount), 2) as 平均业绩,
    SUM(amount) as 总业绩,
    MAX(amount) as 最大业绩,
    MIN(amount) as 最小业绩,
    department as 部门 
    FROM store_perf GROUP BY department

    课后作业

    • 简单使用Mysql约束, 并举例说明使用场景.
    • 练习使用分组查询.

    变量声明

    • 服务器系统变量

      • 通过@@来调用系统变量
        # 列出mysql所有系统变量
        SHOW VARIABLES
        SELECT @@date_format
    • 用户变量

      • 通过@来调用用户变量
        # 输出变量yesterday
        SELECT @yesterday
        
        # 对变量yesterday进行赋值
        SET @yesterday=subdate(CURRENT_DATE, 1);
        
        # 日期的格式化
        SET @yesterday=DATE_FORMAT(@yesterday, "%Y/%m/%d");
        
        # 小数的格式化
        SET @amount=0.4;
        SET @amount=CAST(@amount as DECIMAL(15, 3));
        SELECT @amount
    • 局部变量

      • 不需要@前缀
      • 需要事先进行变量类型的声明和初始化

    存储过程

    简单地认为是SQL中的函数

    • 声明一个存储过程

      • 创建存储过程
        • 每一句语句结束之后都要添加分号;
        CREATE PROCEDURE stat_store_perf(days INT)
        BEGIN
        	DECLARE t_date VARCHAR(255);
        	set t_date = DATE_FORMAT(subdate(CURRENT_DATE, days), '%Y-%m-%d');
        	SELECT count(*) as 店铺数, 
        	FORMAT(AVG(amount), 2) as 平均业绩,
        	SUM(amount) as 总业绩,
        	MAX(amount) as 最大业绩,
        	MIN(amount) as 最小业绩,
        	department as 部门 
        	FROM store_perf WHERE sta_date=t_date
        	GROUP BY department;
        END
    • 调用存储过程

      CALL stat_store_perf(1)
    • 删除存储过程

      DROP PROCEDURE stat_store_perf

    触发器

    和存储过程一样, 都是嵌入到mysql中的一段程序, 区别就是存储过程需要显式调用, 而触发器式根据对表的相关操作自动激活执行.

    • 创建触发器

      CREATE TRIGGER 触发器名
      BEFORE[AFTER] [INSERT, UPDATE, DELETE]
      
      CREATE TRIGGER check_department
      BEFORE INSERT
      ON store_perf
      FOR each row
      BEGIN
      	IF new.department not in ("男装事业部", "女装事业部") THEN
      	SET new.department 'unknow';
      	END IF;
      END
    • 触发器的应用
      多用来检查字段

    • 查看触发器

      建议通过客户端删除

      SHOW TRIGGERS FROM store_perf
    • 删除触发器

      建议通过客户端删除

      DROP TRIGGERS check_department

    课后作业

    • 重点掌握用户变量, 练习变量的赋值和使用
    • 练习存储过程(能自行完整地写出一个函数)
    • 练习触发器(只要求操作几次)

    用python连接数据库

    • pymysql

      pip install pymysql
      
      #如果让你装vs环境, 执行以下命令升级pip即可
      python -m pip install --upgrade pip
    • 连接数据库

      • 数据库设置

        MYSQL_CONF = {
            "host": "127.0.0.1",
            "user": "root",
            "password": "qwe369",
            "db": "test"
        }
      • 连接

        # 连接数据库
        mysql_con = pymysql.connect(**MYSQL_CONF)
        
        # 简单理解真正执行语句的线程
        mysql_cursor = mysql_con.cursor()
    • 执行SQL语句

      • 单条执行

        执行时间15.31s

        @clock_it_deco
        def insert_one():
            for i in range(10**4):
                store_name = f"店铺_{i}"
                amount = format(random.uniform(10**3, 10**6), '.2f')
                department = f"事业部_{random.randint(1, 10)}"
                sta_date = time.strftime("%Y-%m-%d")
        
                SQL = f"""INSERT INTO store_perf(store_name, amount, department, sta_date)
                          VALUES ('{store_name}', {amount}, '{department}', '{sta_date}')"""
                print(SQL)
                mysql_cursor.execute(SQL)
        
                # 显示执行commit, 避免cursor执行, 但是数据库未收到的执行指令的情况
                mysql_con.commit()
      • 多条执行

        • 如果其中有一条记录报错, 那么整个values插入都会失败
        @clock_it_deco
        def insert_many():
            values = []
            for i in range(10**4):
                store_name = f"店铺_{i}"
                amount = format(random.uniform(10**3, 10**6), '.2f')
                department = f"事业部_{random.randint(1, 10)}"
                sta_date = time.strftime("%Y-%m-%d")
                values.append((store_name, amount, department, sta_date))
        
            SQL = """INSERT INTO store_perf(store_name, amount, department, sta_date)
                     VALUES (%s, %s, %s, %s)"""
            print(values)
            mysql_cursor.executemany(SQL, values)
            mysql_con.commit()
      • 获取返回值

        def get_shops():
            SQL = "select store_name, amount, sta_date from store_perf where department='事业部_1' LIMIT 2"
            mysql_cursor.execute(SQL)
            # 获取返回值
            query_set = mysql_cursor.fetchall()
            print(query_set)
        • 返回的是一个元组, 元组中表示记录的也是一个元组

          (('店铺_1', Decimal('823471.56'), '2021-02-06'), ('店铺_6', Decimal('726927.02'), '2021-02-06'))

    索引和优化

    • 创建索引

      未建索引: 0.287s

      建了索引后: 0.016s

      • 设置索引的字段不可以超过191个字符长度, 也就是767个bytes
      CREATE INDEX 索引名 ON 表名(字段1, 字段2...)
      
      CREATE INDEX store_name_index ON store_perf(store_name)
    • 创建联合索引

      CREATE INDEX store_name_sta_date_department_index ON store_perf(store_name, sta_date, department)
    • 查看索引

      SHOW INDEX FROM store_perf
    • 删除索引

      ALTER TABLE store_perf DROP INDEX  store_name_index
    • 查看当前查询语句有没有命中索引

      • EXPLAIN语句查看当前语句执行性能
      • 如果key有值, 说明命中了索引, 且key值为索引名
      EXPLAIN SELECT * from store_perf WHERE store_name = "店铺_224123"
    • 单个字段可以命中联合索引吗?
      联合索引涉及到一个叫左缀查询的规则

      如果想命中索引, 查询语句中涉及到字段必须是联合索引创建时从左到右顺序

      • 原理
        a_b_c_index这样一个联合索引当中, 实质执行中是先查出a的结果集, 然后再查bc的结果集(第一个字段匹配,才匹配后面的子字段)
    • 索引的实现原理
      B+树, 一种特殊的链表, 用来实现二分查找.

    如何优化mysql

    • 合理地建立索引

      • 频繁作为查询条件的字段应该建立索引
      • 唯一性太差的字段不适合单独建立索引
      • 更新非常频繁的字段不适合建立索引
      • 避免不经过索引的操作
        • not in, !=等反向逻辑
        • BETWEEN范围查找
        • or逻辑两边都必须命中索引才会走索引
        • 联合索引, 不按左缀查询规则
    • 加缓存

      • 数据库缓存

        show VARIABLES LIKE '%query_cache%'
      • 用redis做缓存

        请求 -> redis -> 未命中 -> mysql -> 返回
    • 换固态硬盘

    事务

    • 事务的提交, 回滚

      START TRANSACTION;
      INSERT INTO store_perf(store_name, amount, department, sta_date) VALUES ('店铺_1764', 753294.41, '事业部_8', '2021-02-05');
      ROLLBACK;
      COMMIT;
    • 业务中使用事务

      将class_2中的同学转移到class_1, 如果SQL_2报错, 会导致class_2中的同学丢失.

      def transaction():
          try:
              SQL = "DELETE FROM class_2 where name='name_13'"
              mysql_cursor.execute(SQL)
              SQL_2 = "INSERT INTO class_1 VALUES(name)"
              mysql_cursor.execute(SQL_2)
      
          except Exception as e:
              print("raise Exceptions", e.args[0])
              print("rollback")
              mysql_con.rollback()
          finally:
              mysql_con.commit()

    课后作业

    • 练习pymysql的使用, 报错单语句执行, 多语句执行, 查询语句, 事务的使用
    import pymysql
    import random
    import time
    from base.fun.time_deco import time_deco
    
    # 定义MYSQL连接参数
    MYSQL_CONF = {
        "host": "127.0.0.1",
        "user": "root",
        "password": "1",
        "db": "test"
    }
    
    # mysql_con =pymysql.connect(host='127.1',user='root')
    # 通过字典的方式传参
    # 连接数据库
    mysql_con = pymysql.connect(**MYSQL_CONF)
    
    # 真实执行语句的线程
    mysql_cursor = mysql_con.cursor()
    
    
    @time_deco
    def create_table():
        SQL = f"""CREATE TABLE if not exists `store_perf`  (
                      `id` int(11) NOT NULL AUTO_INCREMENT,
                      `store_name` varchar(191) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
                      `department` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
                      `amount` decimal(15, 2) NULL DEFAULT NULL,
                      `sta_date` varchar(191) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
                      `create_time` datetime(0) NOT NULL DEFAULT current_timestamp(),
                      PRIMARY KEY (`id`) USING BTREE
                  ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
                """
        mysql_cursor.execute(SQL)
        mysql_con.commit()
    
    
    def delete_table():
        SQL = '''truncate table store_perf;'''
    
    
    # 单条SQL插入,单条执行
    @time_deco
    def insert_one():
        for i in range(10 ** 2):
            store_name = f"店铺_{i}"
            # 生成资金
            amount = format(random.uniform(10 ** 3, 10 ** 6), '.2f')
            department = f"事业部_{random.randint(1, 10)}"
            sta_date = time.strftime("%Y-%m-%d")
    
            SQL = f"""INSERT INTO store_perf(store_name, amount, department, sta_date)
                      VALUES ('{store_name}', {amount}, '{department}', '{sta_date}')"""
            # print(SQL)
            mysql_cursor.execute(SQL)
    
            # 显示执行commit, 避免cursor执行, 但是数据库未收到的执行指令的情况
            mysql_con.commit()
    
    
    # 多条SQL插入,如果其中有一条记录报错, 那么整个values插入都会失败
    @time_deco
    def insert_many():
        values = []
        for i in iter(range(10 ** 2)):
            store_name = f"店铺_{i}"
            amount = format(random.uniform(10 ** 3, 10 ** 6), '.2f')
            department = f"事业部_{random.randint(1, 10)}"
            sta_date = time.strftime("%Y-%m-%d")
            values.append((store_name, amount, department, sta_date))
    
        SQL = """INSERT INTO store_perf(store_name, amount, department, sta_date)
                 VALUES (%s, %s, %s, %s)"""
        # print(values)
        # print(SQL, values)
        mysql_cursor.executemany(SQL, values)
        mysql_con.commit()
    
    
    # 获取返回值
    # 返回的是一个元组, 元组中表示记录的也是一个元组
    def get_shops():
        SQL = "select store_name, amount, sta_date from store_perf where department='事业部_1' LIMIT 2"
        mysql_cursor.execute(SQL)
        # 获取返回值
        query_set = mysql_cursor.fetchall()
        print(query_set)
    
    
    if __name__ == '__main__':
        # create_table()
        # delete_table()
        # insert_one()
        # insert_many()
        get_shops()
    
    • 练习索引的创建
    • 将索引的内容背诵
  • 相关阅读:
    mouseOver与rollOver
    排序二叉树
    发展
    bitmapData
    回调与事件
    遍历舞台上所有对象
    面向对象原则
    面向对象的三大特征(个人理解)
    面向对象出现原因以及意义
    OC语言BLOCK和协议
  • 原文地址:https://www.cnblogs.com/final233/p/15751894.html
Copyright © 2011-2022 走看看