• 2018.11.27数据库索引,元类,单例


    一.索引

    1.1什么是索引

    索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容

    搜索引导, 所以是一种单独的,物理的 有序的 存储结构,用于加速查询

            例如: 字典  书的目录  车票上的车厢号

    1.2为什么需要索引

    一个项目正常运行后,对数据库的操作中,哪些操作是最频繁的?

    数据的读操作会更加频繁,比例在10:1左右,也就是说对数据库的查询操作是非常频繁的

    随着时间的推移,表中的记录会越来越多,此时如果查询速度太慢的话对用户体验是非常不利

    因为项目运行中,查询操作非常频繁,为了提高用户体验,要提高查询的速度,

    简单的说索引的就是用帮我们加快查询速度的

    1.3索引带来的影响

      1.加速查询

      2.降低写入(增加 删除 修改)速度

      3.会额外占用磁盘空间

    1.4索引数据结构剖析

    索引最终的目的是要尽可能降低io次数,减少查找的次数,以最少的io找到需要的数据,此时B+树闪亮登场

    光有数据结构还不行,还需要有对应的算法做支持,就是二分查找法

    有了B+数据结构后查找数据的方式就不再是逐个的对比了,而是通过二分查找法来查找

    1.5聚集索引

    1.聚集索引 就是主键索引

    行中的完整记录存在聚集索引的叶子节点上

    叶子节点保存的就是完整的一行记录,如果设置了主键,主键就作为聚集索引,

    如果没有主键,则找第一个NOT NULL 且QUNIQUE的列作为聚集索引,

    1.6辅助索引 除了主键的索引

    除了聚集索引之外的索引都称之为辅助索引或第二索引,包括 foreign key 与 unique

      叶子节点 存储索引字段的值 还有 主键的值

      使用辅助索引时 会产生两种现象

      1.回表    要查的数据就不在辅助索引中 需要到聚集索引中查找

    (获得所在行的主键值回到聚集索引再取查找其他字段称为回表)

      2.覆盖索引  要查的数据就在辅助索引中

    查询速度对比:

    聚集索引 > 覆盖索引 > 非覆盖索引(回表)

    1.7正确使用索引

    案例:

    首先准备一张表数据量在百万级别

    create table usr(id int,name char(10),gender char(3),email char(30));

    #准备数据

    delimiter //

    create procedure addData(in num int)

    begin

    declare i int default 0;

    while  i < num do

        insert into usr values(i,"jack","m",concat("xxxx",i,"@qq.com"));   

    set i  = i + 1;

    end while;

    end//

    delimiter ;

    #执行查询语句 观察查询时间
    select count(*) from usr where id = 1;
    #1 row in set (3.85 sec)
    #时间在秒级别 比较慢
     
    1.
    #添加主键
    alter table usr add primary key(id);
    #再次查询
    select count(*) from usr where id = 1;
    #1 row in set (0.00 sec)
    #基本在毫秒级就能完成 提升非常大
     
    2.
    #当条件为范围查询时(范围查询不能优化必须把所有表查询一边)
    select count(*) from usr where id > 1;
    #速度依然很慢 对于这种查询没有办法可以优化因为需要的数据就是那么多
    #缩小查询范围 速度立马就快了
    select count(*) from usr where id > 1 and id < 10;
    #当查询语句中匹配字段没有索引时 效率测试
    select count(*) from usr where name = "jack";
    #1 row in set (2.85 sec)
    # 速度慢
     
     
    3.
    # 为name字段添加索引
    create index name_index on usr(name);
    # 再次查询
    select count(*) from usr where name = "jack";
    #1 row in set (3.89 sec)
    # 速度反而降低了 为什么?(name字段里所有的记录都是jack所以相同的太多添加反而增加I0操作所以速度反而低了)
    #由于name字段的区分度非常低 完全无法区分 ,因为值都相同 这样一来B+树会没有任何的子节点,像一根竹竿每一都匹配相当于,有几条记录就有几次io ,所有要注意 区分度低的字段不应该建立索引,不能加速查询反而降低写入效率,
    #同理 性别字段也不应该建立索引,email字段更加适合建立索引
     
    # 修改查询语句为
    select count(*) from usr where name = "aaaaaaaaa";
    #1 row in set (0.00 sec) 速度非常快因为在 树根位置就已经判断出树中没有这个数据 全部跳过了
    # 模糊匹配时
    select count(*) from usr where name like "xxx"; #快(树根位置就判断出没有这个数据)
    select count(*) from usr where name like "xxx%"; #快(树根位置就判断出没有这个数据)
    select count(*) from usr where name like "%xxx"; #慢(所有都匹配了一边)
    #由于索引是比较大小 会从左边开始匹配 很明显所有字符都能匹配% 所以全都匹配了一遍
    4.索引字段不能参加运算
    select count(*) from usr where id * 12 = 120;
    #速度非常慢原因在于 mysql需要取出所有列的id 进行运算之后才能判断是否成立
    #解决方案
    select count(*) from usr where id = 120/12;
    #速度提升了 因为在读取数据时 条件就一定固定了 相当于
    select count(*) from usr where id = 10;
    #速度自然快了
    5.有多个匹配条件时 索引的执行顺序  and 和 or
    #先看and
    #先删除所有的索引
    alter table usr  drop primary key;
    drop index name_index on usr;
    #测试
    select count(*) from usr where name = "jack" and gender = "m" and id = 1 and email = "xxxx2@qq.com";
    #1 row in set (1.34 sec) 时间在秒级 
    #为name字段添加索引
    create index name_index on usr(name);
    #测试
    select count(*) from usr where name = "jack" and gender = "m" and id = 1 and email = "xxxx2@qq.com";
    #1 row in set (17.82 sec) 反而时间更长了(记录内容相同太多区分度低)
    #为gender字段添加索引
    create index gender_index on usr(gender);
    #测试
    select count(*) from usr where name = "jack" and gender = "m" and id = 1 and email = "xxxx2@qq.com";
    #1 row in set (16.83 sec) gender字段任然不具备区分度(记录内容相同太多区分度低)
    #为id加上索引
    alter table usr add primary key(id);
    #测试
    select count(*) from usr where name = "jack" and gender = "m" and id = 1 and email = "xxxx1@qq.com";
    #1 row in set (0.00 sec) id子弹区分度高 速度提升
    #虽然三个字段都有索引 mysql并不是从左往右傻傻的去查 而是找出一个区分度高的字段优先匹配
    #改为范围匹配
    select count(*) from usr where name = "jack" and gender = "m" and id > 1 and email = "xxxx1@qq.com";
    #速度变慢了
    #删除id索引 为email建立索引
    alter table usr drop primary key;
    create index email_index on usr(email);
    #测试
    select count(*) from usr where name = "jack" and gender = "m" and id = 1 and email = "xxxx2@qq.com";
    #1 row in set (0.00 sec) 速度非常快(记录内容区分度高)
     
    #对于or条件 都是从左往右匹配 
    select count(*) from usr where name = "jackxxxx" or email = "xxxx0@qq.com";
    #注意 必须or两边都有索引才会使用索引 

    使用or的时候  如果两边都有索引 会使用索引,但是注意 or两边都要执行

    顺序依然从左往右.  只有一边有索引会不会使用索引?  不会使用 无法加速查询

    6.多字段联合索引
    为什么需要联合索引
    案例:
    select count(*) from usr where name = "jack" and gender = "m" and id  > 3 and email = "xxxx2@qq.com";
    假设所有字段都是区分度非常高的字段,那么除看id为谁添加索引都能够提升速度,但是如果sql语句中没有出现索引字段,那就无法加速查询,最简单的办法是为每个字段都加上索引,但是索引也是一种数据,会占用内存空间,并且降低写入效率
    此处就可以使用联合索引,


    联合索引最重要的是顺序 按照最左匹配原则 应该将区分度高的放在左边 区分度低的放到右边

    #删除其他索引
    drop index name_index on usr;
    drop index email_index on usr;
    drop index gender_index on usr;
    #联合索引
    create index mul_index on usr(email,name,gender,id);
    # 查询测试
    select count(*) from usr where name = "xx" and id = 1 and email = "xx";
    只要语句中出现了最左侧的索引(email) 无论在前在后都能提升效率 
    drop index mul_index on usr;
     

           

    索引总结:

    索引的正确使用姿势

    命中索引 ,条件中有索引字段匹配上

    0优先使用聚集索引

    1.无论索引如何设计 无法降低范围查询的查询速度

        select count(*) from usr where id > 1;

    即使命中索引也无法提高效率

    2.索引不应该加在值重复度很高的字段上 应该加在重复度低的字段,索引的字段数据量应该尽可能小

    3. 使用and时 当 条件中出现多个索引命中时  会自定找一个区分度最高的索引来使用

    4.使用or的时候  如果两边都有索引 会使用索引,但是注意 or两边都要执行  顺序依然从左往右,只有一边有索引会不会使用索引?  不会使用 无法加速查询

    5.优化查询 不仅仅要加索引,sql语句也需要优化 使其能命中索引

    你的条件中应该使用区别度高的索引

    6.联合索引

    为是什么使用它

    降低资源的占用 , 降低增删改的时间   会比单个字段的索引快

    建立联合索引时 应该把区分度高放最左边  区分度低的依次往右放

    按照区分度的高低 从左往右  依次排列

    查询中 把区分度高放左边 查询时 尽可能使用最左边的索引

    使用and时 无所谓书写顺序 会自动找区分度最高的

    注意联合索引在查询时  如果压根没用到最左侧索引 不能加速查询

    缺点

            索引越多越好?

         索引能够提升效率

        同时降低了写入速度

    增加额外的磁盘占用

    正常开发时*******

    优先使用聚集索引

    再次 使用联合索引  如果你的条件不包含最左侧索引  不能加速查询 这时候就应该使用单个字段索引

    二.  元类

    class Dog:
        def __init__(self):
            print("狗初始化了")
        color = "red"
        def talk(self):
            print("狗在叫!")
    d1 = Dog()
    #print(Dog.__dict__)
    #d1.talk()
    # 查看对象时哪个类实例化出来的
    print(d1.__class__)# <class '__main__.Dog'>
    print(Dog.__class__)<class 'type'>
    print(type(d1)) # <class '__main__.Dog'>
    print(type(Dog)) <class 'type'>
    可以推导出===>产生DOG的过程一定发生了:Teacher=type(...)

    用于实例化产生类的类称之为元类就是此时的type;所有类产生时都被tpye实例化

    DOG是通过type实例化得到的,既然如此,是不是可以自己调用type来实例化一个calss呢?

    2.创建类的流程分析

    class关键字在帮我们创建类时,必然帮我们调用了元类Teacher=type(...),那调用type时传入的参数是什么呢?必然是类的关键组成部分,一个类有三大组成部分,分别是

    1、 类名class_name='Teacher

    2、 基类class_bases=(object,)

    3、 类的名称空间class_dic,类的名称空间是执行类体代码而得到的

    class_name = "pig"
    bases = (object,)
    pic_dict = {}
    class_body = """
    def __init__(self):
        print("猪初始化了")

    color = "red"

    def talk(self):
        print("猪在叫!")
    """
    # 执行一堆字符串代码  将生产的内容放到pic_dict中
    exec(class_body,{},pic_dict)#
    # print(pic_dict,type(pic_dict))
    # 调用type产生一个类
    c = type(class_name,bases,pic_dict)
    print(c)
    print(Dog)#和上面自己定义一个类输出的结果是一样的
    """
        默认情况下  所有的类都是通过type这个元类示例化的
        我们完全可以自己来实例化
        元类的作用?
        用于创建类的类 称为元类
    """
     
    3.通过元类来控制类的创建过程
    class MyMetaclass(type):
               # 什么时候执行?  MyMetaclass 定义一个类是 系统会自动去调用元类
               def __init__(cls, class_name, bases, namespace):
                                    # 既然创建类时 会自动执行 该方法 那完全编写一些逻辑在init中 来控制类的创建
                                    # 首字母必须大写否则不让创建
                                    if not class_name.istitle():
                                                         raise TypeError("类名首字母必须大写!")
     
                                    if object not in bases:
                                                         raise TypeError("必须显式的继承object")
     
                                    print(cls)
                                    print(class_name)
                                    print(bases)#object写在定义类里
                                    print(namespace)
     
     
    # 当求解释器执行到这行diamante时  自动调用了MyMetaclass
    class Foo(metaclass=MyMetaclass):  # 等同于 Foo = MyMetaclass()
               attr = 123
               pass
    class foo(metaclass=MyMetaclass):#首字母小写报错
               attr = 123
               pass
    class Foo1(object,metaclass=MyMetaclass): # 等同于 Foo = MyMetaclass()
               attr = 123
               pass
     
     
     
     
     
    4自定义元类控制类的调用
    控制类的调用过程 关键在于call函数, 类也是对象,调用类必然也会执行call函数

    Class Bar:
        def __call__(self, *args, **kwargs):
            print("run call")
    # 调用类时  还是 调用对象时执行
    b1 = Bar()
    b1()
    print(b1)

    # 在调用对象时 自动触发__call__的执行
    # 推导 b1是Bar的实例   调用b1 会触发Bar中的__call__
    # Bar 是type的实例  调用Bar 应当触发type中的__call__
     
    # 类中的__init__也被拦截了
    class MyMetaClass(type):

        def __call__(self, *args, **kwargs):
            print("MyMetaClass __call__ run!")
            print(self)

            #需求 判断实例化时的参数必须是字符串类型
            #isinstance(args[0],str)
            if type(args[0]) != str:
                raise TypeError("姓名必须是字符串类型!")
            # 这是自定义元类时  必须要有的模板  以保证可以正常实例化产生对象
            obj = object.__new__(self)
            obj.__init__(*args,**kwargs)#被拦截
            return obj

    class Foo(metaclass=MyMetaClass):
        def __init__(self,name):
            self.name = name
        pass
    # 调用类本质上就是在调用__call__ 其返回值表示实例化得到的对象
    res = Foo("a")
    print(res)

    # 调用一个类   创建出一个空对象,调用__init__来完成对象的初始化,返回该对象
    # 控制类的调用 也就是实例化过程    核心函数 元类中的__call__
    # 需要注意的是,在__call__中应当先完成基础的逻辑 1.创建空对象,2.执行__init__ 3.返回新对象
    # 在此基础上添加额外的业务逻辑
     
     

    三 单例

    什么是单例,

    单例是指的是单个实例,指的是 一个类有且仅有一个实例 就叫单例为什么要用单例

    实现单例 就通过判断是否已经创建过对象

    为什么要用单例

    当一个类的实例中的数据不会变化时使用单例,数据是不变的

    例如开发一个音乐播放器程序,音乐播放器可以封装为一个对象,那你考虑一下,当你切歌的时候,是重新创建一个播放器,还是使用已有的播放器?

    因为播放器中的数据和业务逻辑都是相同的没有必要创建新的,所以最好使用单例模式,以节省资源,

    class User:

        def __init__(self,name,age,sex):

            self.name = name

            self.age = age

            self.sex = sex

        instance = None

        # 通过指定的方法来获取实例 而不是调用类来创建新对象

        @classmethod

        def get_instance(cls,name,age,sex):

            if not cls.instance:

                cls.instance = cls(name,age,sex)

            return cls.instance

        @staticmethod

        def get_instance2(name,age,sex):

            if not User.instance:

                User.instance = User(name,age,sex)

            return User.instance

    # u1 = User("张三",20,"man")

    # u2 = User("张三",20,"man")

    u1 = User.get_instance("张三",20,"man")

    print(u1)# <__main__.User object at 0x0038CFF0>

    #通过方法可以保证u1和u2对象的一致,但不能阻止使用者直接用类来生成新的对象.

    u2 = User.get_instance2("张三",20,"man")

    print(u2)# <__main__.User object at 0x0038CFF0>

    # 通过classMethod 可以完成单例  但是还是可以通过直接调用;类产生新对象  此时就需要用到元类

    u3 = User("张三",20,"man")#<__main__.User object at 0x003A3030>

    #u3和上面u1.u2不一致使用者还是可以通过直接调用类产生新对象

    该方法无法避免使用者直接调用类来实例化,这样就不是单例了

    使用元类创造单利

    class MyMetaClass(type):

        instance = None
        def __call__(cls, *args, **kwargs):
            if not MyMetaClass.instance:
                # 创建空对象
                MyMetaClass.instance = object.__new__(cls)
                print("创建新的播放器对象!")
                #初始化对象
                MyMetaClass.instance.__init__(*args,**kwargs)
                                    #arg收到的参数是下面init,属性里定义的参数一致,
                # 返回对象
            return MyMetaClass.instance

    # 只能有一个播放器实例
    class CDPlayer(metaclass=MyMetaClass):
        def play(self,music):
            print("切换音乐",music)
        def __init__(self,music_name):
            self.music_name = music_name

    p1 = CDPlayer("你发如雪!")
    p2 =CDPlayer("夜曲")
    print(p1)# <__main__.CDPlayer object at 0x008B90B0>
    print(p2)# <__main__.CDPlayer object at 0x008B90B0>
    p1.play("菊花台")
    p1.play("菊花台2")
    p1.play("菊花台3")

    # 元类实现单例 就是拦截了元类中的__call__的正常执行  使得创建对象都必须经过自己判断逻辑(这样就不能通过类(CDPlay)创建新的对象)

    # 类中的__init__也被拦截了

  • 相关阅读:
    [转]VS2010几款超赞的扩展辅助工具总结
    从客户端中检测到有潜在危险的Request.Form 值
    面试系列28 分布式服务接口的幂等性如何设计
    面试系列26 如何基于dubbo进行服务治理、服务降级、失败重试以及超时重试
    面试系列25 dubbo的spi思想是什么
    面试系列24 dubbo负载均衡策略和集群容错策略
    面试系列23
    面试系列22 dubbo的工作原理
    面试系列21 为什么要进行系统拆分
    面试系列20 生产环境中的redis是怎么部署的
  • 原文地址:https://www.cnblogs.com/jutao/p/10064332.html
Copyright © 2020-2023  润新知