MYSQL 索引选择性陷阱

MYSQL 索引选择性陷阱

场景

用户表有1000个用户,其中有一个sex(性别)字段。sex字段上有一个sex索引。

但用户表有超过60%的用户sex为1(男性)

此时,执行语句

布隆过滤器的理论基础及Redisson实现

如果想要判断一个元素是不是在一个集合里,一般想到的是将所有元素保存起来,然后通过比较确定。链表,树等等数据结构都是这种思路. 但是随着集合中元素的增加,我们需要的存储空间越来越大,检索速度也越来越慢(O(n),O(logn))。不过世界上还有一种叫作散列表(又叫哈希表,Hash table)的数据结构。它可以通过一个Hash函数将一个元素映射成一个位阵列(Bit array)中的一个点。这样一来,我们只要看看这个点是不是1就可以知道集合中有没有它了。这就是布隆过滤器的基本思想。

Hash面临的问题就是冲突。假设Hash函数是良好的,如果我们的位阵列长度为m个点,那么如果我们想将冲突率降低到例如 1%, 这个散列表就只能容纳m / 100个元素。显然这就不叫空间效率了(Space-efficient)了。解决方法也简单,就是使用多个Hash,如果它们有一个说元素不在集合中,那肯定就不在。如果它们都说在,虽然也有一定可能性它们在说谎,不过直觉上判断这种事情的概率是比较低的。

img
img
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;

public class BloomFilter {
    private static final int DEFAULT_SIZE = 2 << 24;
    private static final int[] seeds = new int[] { 5, 7, 11, 13, 31, 37, 61 };
    private BitSet bits = new BitSet(DEFAULT_SIZE);
    private SimpleHash[] func = new SimpleHash[seeds.length];

    public BloomFilter() {
        for (int i = 0; i < seeds.length; i++) {
            func[i] = new SimpleHash(DEFAULT_SIZE, seeds[i]);
        }
    }

    public void add(String value) {
        for (SimpleHash f : func) {
            bits.set(f.hash(value), true);
        }
    }

    public boolean contains(String value) {
        if (value == null) {
            return false;
        }
        boolean ret = true;
        for (SimpleHash f : func) {
            ret = ret && bits.get(f.hash(value));
        }
        return ret;
    }

    // 内部类,simpleHash
    public static class SimpleHash {
        private int cap;
        private int seed;

        public SimpleHash(int cap, int seed) {
            this.cap = cap;
            this.seed = seed;
        }

        public int hash(String value) {
            int result = 0;
            int len = value.length();
            for (int i = 0; i < len; i++) {
                result = seed * result + value.charAt(i);
            }
            return (cap - 1) & result;
        }
    }

    public static void main(String[] args) {
        BloomFilter bf = new BloomFilter();
        List<String> strs = new ArrayList<String>();
        strs.add("123456");
        strs.add("hello word");
        strs.add("transDocId");
        strs.add("123456");
        strs.add("transDocId");
        strs.add("hello word");
        strs.add("test");
        for (int i=0;i<strs.size();i++) {
            String s = strs.get(i);
            boolean bl = bf.contains(s);
            if(bl){
                System.out.println(i+","+s);
            }else{
                bf.add(s);
            }
        }
    }

}

布隆过滤器的应用

缓存一致性方案

1.延时双删 删除缓存 更新数据库 sleep 500ms 删除缓存 2.读取mysql的binlog,订阅数据库更新日志。

分布式锁的实现

分布式锁的实现方式

1 数据库乐观锁

利用表的唯一索引行级锁进行加解锁,加锁:

insert into methodLock(method_name,desc) values (‘method_name’,‘desc’)

释放锁,需要执行以下Sql:

uuid导致的MySQL性能问题

uuid无序导致Inoodb页分裂和随机IO

uuid是无序的,当uuid可能在索引中的某一页插入数据时,新增记录所在的数据页已满,数据库需要申请一个新的数据页存储数据,这种现象被称为“页分裂”

页分裂确保后一个数据页中的所有id值一定比数据页中的id值大,在大并发环境下增加了磁盘IO的压力(随机访问),无序才是罪魁祸首。

解决办法:改为有序的数字主键生成策略就可以了。如美团的Leaf/推特的Snowflake算法

随机IO:数据在磁盘分布不均匀,导致访问数据的时候,磁盘磁尖要多转几圈才能访问得到数据。

MYSQL MVCC & LOCK

MYSQL的MVCC并发控制

行锁

MYSQL的行锁是基于索引加载的,所以行锁要加在索引响应的行上。

特征:

锁冲突概率低,并发性高,但是会有死锁的情况出现。

Transaction isolation level

事务的隔离级别

事务是必须满足4个条件(ACID)::原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。

  • 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
  • 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
  • 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
  • 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

数据库事务并发问题

假设选择有两个事务:Transaction01和Transaction02并发执行.

MYSQL

MYSQL索引