数据库

工作经验

mysql默认不区分大小写

select * from user where binary name = 'abc'
select * from user where binary name = 'ABC'
注:binary是一种二进制数据类型,关于name的varchar索引会失效

指定导出条件

先连接mysql;
SELECT active_code,user_id from active_code where id > 817708 INTO OUTFILE 'export.sql';
注意:导出的是空格分割的纯数据,并不是SQL

mysql索引注意事项

    -- partner_store_no已存在索引,且是varchar类型:100万的表
    SELECT * from active_code where partner_store_no = 1300593 -->不会触发索引,耗时>1秒
    SELECT * from active_code where partner_store_no = '1300593' -->会触发索引,耗时<0.1秒
    结论:varchar类型索引,搜索时不要用int类型(索引失效),完全是两种不同的比较方式
    varchar类型,索引长度有限,不能超过191个字符(mysql做的是字节限制,除以4约等于191字符)

很多时候null/not null不触发唯一索引

""会触发唯一索引,而null不一定。
invoice_request表测试结果:apply_id(bigint) 的null和not null都能触发索引;pyt_settlement_no的null会触发,而not null不会触发

SQL疑惑-update含子查询时效率很低

需求1(partner_store_no是有索引的):
UPDATE active_code 
SET user_id = 38 
WHERE
    partner_store_no IN (select查询条件)
    
想通过创建临时表提高性能(不好使):
create TEMPORARY table jin_jiang_table (
    SELECT
        company_store.store_no 
    FROM
        company JOIN company_store ON company_store.company_id = company.id 
    WHERE company.sub_source = 1 
)

最后把数据直接粘出来了用(性能提高几百倍)
UPDATE active_code 
SET user_id = 38 
WHERE
    partner_store_no IN (1,2,3,4,5...)
-- 注:借助join可以提高含子查询的update效率

需求2:
set @companyId = 100001386;
set @minId = (SELECT min(id) from company_tax_disk where company_id = @companyId and terminal_no = '-1');
update invoice_request set tax_disk_id = @minId where tax_disk_id in 
(SELECT id from company_tax_disk where company_id = @companyId and terminal_no = '-1' and id != @minId);
-- 注:该update写法会走invoice_request表全表扫描,巨慢。

update invoice_request join company_tax_disk on invoice_request.tax_disk_id = company_tax_disk.id
set tax_disk_id = @minId 
where company_tax_disk.id in
 (SELECT id from company_tax_disk where company_id = @companyId and terminal_no = '-1' and id != @minId)
-- 注:update/delete存在子查询时,借助join就可以提高性能。

update多表关联

    update a, b set a.c1 = b.c2 where a.c3 = b.c4;
    业务复杂时,b表可以是临时表

mysql编码集引发的问题

    向mysql存储数据时,里边有icon,utf-8的数据库编码集不支持,就会报错:\0x12\0xs8\0x30\0xis...数据不正确。
    要用utf8mb4:要求库、表、字段都要是utf8mb4字符集。字段可以使用varchar和text,java使用String即可[java原生支持]

SQL统计[sum函数的妙用]

化null为0,化非null为1,group by之后sum一下,千万别用成count了

    需求:统计酒店开票量
    SELECT
        a. NAME AS 酒店名称,
        sum(
            CASE
            WHEN c.id IS NULL THEN
                0
            ELSE
                1
            END
        ) AS 开票数量
    FROM
        company_store a
    LEFT JOIN invoice_request b ON a.id = b.store_id
    LEFT JOIN invoice_entry c ON c.request_id = b.id
    WHERE
        a. NAME IN (
            '安康假日酒店',
            '献县商务酒店',
            '孟州网络宾馆'
        )
    GROUP BY
        a.id
storeName requestId entryId
安康假日酒店 1001 1298
安康假日酒店 1102 null
安康假日酒店 1049 1404
献县商务酒店 1244 3123
献县商务酒店 1456 null
孟州网络宾馆 null null

mysql update语句clause问题

    update user set age = age + 20 where id in (select id from user where age < 10);
    报错:update语句,where条件后边不能有target表的子句。
    方案:借助临时表。
    create temporary table tem (select id from user where age < 10);
    update user set age = age + 20 where id in (select id from tem);

mysql json array操作

mode字段的0号位置换成4
UPDATE invoice_request SET mode = JSON_REPLACE (mode, '$[0]', 4) where ...

Left join的普通条件放在on后边[左外连接]和where后边[内连接]的区别【TODO】

mysql in关键字有null值会索引失效

where name in (...)  -- 要求不能有null值,否则in失效

SQL 统计invoice_code + invoice_no相同,而status不同的数据

    SELECT
        invoice_code,
        invoice_no,
        min(STATUS) AS min,
        MAX(STATUS) AS max
    FROM
        (
            SELECT DISTINCT
                invoice_code,
                invoice_no,
                `status`
            FROM
                `extract_invoice`
        ) a
    GROUP BY
        invoice_code,
        invoice_no
    HAVING min != max

group时,怎么把id都统计出来。最长容纳1024个字符

select GROUP_CONCAT(id) from user group by address;     -- 默认用逗号分隔

DATE_FORMAT

DATE_FORMAT(create_time, "%Y-%m-%d")

区分is null和= null

SQL = null(查不出)和is null(能查出)

json复杂操作(把http换成https)

UPDATE invoice_entry ie
JOIN (
    SELECT
        id,
        json_unquote(pyt_snapshot -> '$.pdfPath') AS pdfPath,  -- 去除数据两头的""
        REPLACE(json_unquote (pyt_snapshot -> '$.pdfPath'), 'http:', 'https:') as pdfPath2
    FROM
        invoice_entry
    WHERE
        type = 'ce'
    HAVING
        pdfPath LIKE 'http:%'
) tem   -- 借助临时表辅助update操作
on ie.id = tem.id
set ie.pyt_snapshot = json_set(ie.pyt_snapshot, '$.pdfPath', tem.pdfPath2);

筛选A字段包含B字段的记录

select * from t where cargo_name NOT LIKE CONCAT('%', abbreviation, '%');  -- where条件不一定要写死,可以使用函数变的灵活

耗时观察

300万数据,添加索引耗时1分钟左右
300万数据,add column大约3~5分钟

数据库alter【TODO】

在alter表结构(add/drop字段、改非空、调整字段顺序)的过程中,不影响数据的正常CRUD。
update时,更不会影响其他事务的CRUD。

FORCE INDEX

-- 已有idx_createTime_status(create_time, status)索引
SELECT
    *
FROM
    invoice_request FORCE INDEX(idx_createTime_status)  --强制指定索引
WHERE
    create_time > '2019-12-01 00:00:00'
AND STATUS = 2
AND to_client = 1;

mysql行列转换[借助case when]

    需求:统计重试成功的发票报错原因数量,以及重试成功占比
    步骤1:先不加group by,统计fail_reasons和status
        reasons1  status1
        reasons1  status2
        reasons2  status1
        reasons2  status2
        ...
    步骤2:case多次,把2列变成3列;借助group by和sum完成统计   
    SELECT
        fail_reasons,
        sum(case status when 3 then 1 else 0 end) as succCount,
        sum(case when status in (3, 5) then 1 end) as allCount,
        sum(case status when 3 then 1 else 0 end) / sum(case when status in (3, 5) then 1 end) as retrySuccessRate
    FROM
        invoice_request force index(idx_create_time) 
    WHERE
        to_client = 1 AND create_time > '2019-11-01' AND STATUS in (3, 5)
    GROUP BY
        fail_reasons
            ORDER BY retrySuccessRate desc, allCount desc;
            -- order by多个条件时,每个条件都需要一个desc/asc,否则该条件默认是asc

存储过程

示例1:
DROP PROCEDURE IF EXISTS xxx;
CREATE PROCEDURE xxx ()
BEGIN

        DECLARE companyId BIGINT;
        DECLARE companyIds CURSOR FOR (
          SELECT company_id FROM `company_tax_disk` WHERE terminal_no = '1' GROUP BY company_id HAVING COUNT(*) > 1
        );

        OPEN companyIds;  --打开游标
        FETCH companyIds INTO companyId;
        WHILE (companyId IS NOT NULL) DO
            INSERT INTO xxx_t(id) VALUES(companyId);
            FETCH companyIds INTO companyId;
        END WHILE;
        CLOSE companyIds;  --关闭游标
END;

CALL xxx ();


示例2:
DROP PROCEDURE IF EXISTS xxx;
CREATE PROCEDURE xxx ()
BEGIN

    DECLARE companyId BIGINT;
    DECLARE companyIds CURSOR FOR (
      SELECT company_id FROM `company_tax_disk` WHERE terminal_no = '-1' GROUP BY company_id HAVING COUNT(*) > 1
    );
        

    OPEN companyIds;
    FETCH companyIds INTO companyId;
    WHILE (companyId IS NOT NULL) DO
        set @minId = (SELECT min(id) from company_tax_disk where company_id = companyId and terminal_no = '-1');
        
        insert into ir_t (select invoice_request.* from invoice_request join company_tax_disk on invoice_request.tax_disk_id = company_tax_disk.id 
        where company_tax_disk.id in (SELECT id from company_tax_disk where company_id = companyId and terminal_no = '-1' and id != @minId));

        update invoice_request join company_tax_disk on invoice_request.tax_disk_id = company_tax_disk.id set tax_disk_id = @minId 
        where company_tax_disk.id in (SELECT id from company_tax_disk where company_id = companyId and terminal_no = '-1' and id != @minId);

        FETCH companyIds INTO companyId;
    END WHILE;
    CLOSE companyIds;
END;

begin;
CALL xxx ();


示例3:
set @identifier = 'CYF0000001';
set @identifier2 = 'CYF20200215';
set @abbreviation = '餐饮服务';
set @tax_type_no = '3070401000000000000';
set @tax_type_name = '餐饮服务';

DROP PROCEDURE IF EXISTS xxx;
CREATE PROCEDURE xxx()
BEGIN

    DECLARE companyId BIGINT;
    DECLARE itemName varchar(50);
    DECLARE specX varchar(50);
    DECLARE unitX varchar(50);
    DECLARE originalPrice varchar(30);
        
    DECLARE companyIds CURSOR FOR (
      select DISTINCT i.company_id from item i join company c on c.id = i.company_id where c.is_deleted = 0 and  c.company_type = 1 and i.identifier = @identifier
    );
        
    OPEN companyIds;
    FETCH companyIds INTO companyId;
    WHILE (companyId IS NOT NULL) DO
            select name into itemName from item where company_id = companyId and identifier = @identifier limit 1;  -- 有些时候查出多条数据会导致运行终止而不报错
                select specification into specX from item where company_id = companyId and identifier = @identifier limit 1;
                select unit into unitX from item where company_id = companyId and identifier = @identifier limit 1;
                select original_price into originalPrice from item where company_id = companyId and identifier = @identifier limit 1;
        
        INSERT INTO `item` 
                (`source`,`company_id`,`identifier`,
                `name`,
                `abbreviation`,`tax_type_no`,`tax_type_name`,`tax_rate`,`discount_identification`,`discount_content`,`tax_flag`,
                specification,
                unit,
                original_price)
                VALUES 
                (0,companyId,@identifier2,
                itemName,
                @abbreviation,@tax_type_no,@tax_type_name,0,1,'免税',1,
                specX,
                unitX,
                originalPrice);

        FETCH companyIds INTO companyId;
    END WHILE;
    CLOSE companyIds;
END;

begin;
CALL xxx();

疑惑:

为啥有一条数据就是无法删除(多次操作都巨慢),其他都能删除
delete from company_tax_disk where id = 48442;
怀疑碰到锁

copy表结构

create table xx_t like user;

select now()

select now()  -- 不用from dual

IF-只有两个选择的时候替换case when

SELECT
    IF(STATUS = 1, 1, 0)   
FROM
    invoice_request
WHERE
    create_time > '2019-12-12'
LIMIT 10

常量数组转换成多条查询结果的形式

方式1(不好使,要求辅助表的id从0开始不间断自增):

 借助substring_index函数和辅助表
SELECT
    SUBSTRING_INDEX(
        SUBSTRING_INDEX(
            ',7654,7698,7782,7788',
            ',',
            id + 1
        ),
        ',' ,- 1
    ) AS num
FROM
    kaipiao.client_health
WHERE
    id < LENGTH(',7654,7698,7782,7788') - LENGTH(
        REPLACE (
            ',7654,7698,7782,7788',
            ',',
            ''
        )
    ) + 1

方式2:推荐

借助临时表
CREATE TEMPORARY TABLE T1 (num INT);
INSERT INTO T1 (num) VALUES (7654), (7698), (7782), (7788);

按周统计

SELECT
    WEEK(create_time),
    sum(IF(STATUS = 3, 1, 0)) AS success,
    sum(IF(STATUS != 1, 1, 0)) AS total
FROM
    invoice_request
WHERE
    create_time BETWEEN '2019-12-20' AND '2020-1-10' 
GROUP BY
    WEEK(create_time)
统计结果

除了week()和month()函数,也可以用原生的DATE_FORMAT(create_time,'%Y-%u')统计每周,DATE_FORMAT(create_time,'%Y-%m')统计每月。需要注意:week()函数统计的是当前的第几周,并不是从0开始计数的

视图和临时表区别

视图:一组SQL集,可以认为是SQL变量的提取,只有用到时才会去原表实时查询数据。好处:简化SQL;安全--可以自定义对外暴露的字段;兼容--user表拆成了user_a和user_b表,可以create view as user ... ,对调用者来说无感知。
临时表:会将数据查出来临时存储,节省视图每次的查询时间。

case when踩坑

乐观锁

两种乐观锁:

  1. 基于版本号或时间戳(永远自增的)实现。
    缺点:电商抢购活动时,大量并发进入,会出现大量的用户查询出库存存在,但是却在扣减库存时失败了
    update t_goods set version=version+1 where id=#{id} and version=#{version};


    image.png
  2. 基于条件限制实现[库存数量等条件]
    UPDATE t_goods SET num = num - #{buyNum} WHERE id = #{id} AND num - #{buyNum} >= 0;

500万的表加字段要2~10分钟,表约宽,可能耗时越多,可能还跟索引有关

表在最后添加字段时,不阻塞数据del/insert/update

500万表update100万数据,不阻塞插入

两个表字段字符集不同时,搜索时可以指定字符集使其统一

where buyer_tax_no COLLATE utf8mb4_unicode_ci = xxx

mysql隐式类型转换的坑

SELECT '123456789012345678' = 123456789012345679  => true   科学计数法之后,最后的精度太长被丢弃
SELECT '12345678901' = 12345678902  => false

100条数据1个G,记事本打不开

Navicat导出数据后,开始是drop和create table语句,一定小心,最好对原表数据备份

一个表的数据插入到另一个表

insert into User (select * from User2);   -- 会连带id一块复制。所以id冲突时失败

判断是否存在

limit 1代替count(*)性能更好

索引失效场景

  1. like “%xxx”
  2. 数据类型隐式转换。varchar字段用作数字查询
  3. 不满足最左前缀匹配原则
  4. 取反操作:not null,!=,<>
  5. 走全表扫描比走索引快的时候
  6. or左右表达式都要触发索引
  7. 索引字段做了运算

阿里开发手册禁用select *

增加SQL解析成本、IO成本、网络开销,有时还会错失覆盖索引

多表关联时小表在前大表在后

在 MySQL 中,执行 from 后的表关联查询是从左往右执行的(Oracle 相反),第一张表会涉及到全表扫描。
所以将小表放在前面,先扫小表,扫描快效率较高,在扫描后面的大表,或许只扫描大表的前 100 行就符合返回条件并 return 了。

调整 Where 字句中的连接顺序

MySQL 采用从左往右,自上而下的顺序解析 where 子句。根据这个原理,应将过滤数据多的条件往前放,最快速度缩小结果集。

oceanBase

投票机制:一主两备,数据至少要写入两台机器才算完毕(两台机器同时坏掉才挂)。关键节点用5台机器(过半-3台机器同时坏掉才挂),做好选举
解决传统数据库写入放大(4k)问题
oceanBase完全自研内核-蚂蚁科技。
polarDB基于mysql内核-阿里云,自动读写分离(由代理层先写SQL解析和路由),定于redo log而不是bin log

分布式事务

弱一致性

弱一致性不可怕,我们可以使用状态机制推动业务流转。
eg:支付中 => 收到回调 => 后续业务

binlog格式

主从复制3个线程

mysql协议

代码跟mysql的通信使用的是TCP协议
半双工模式:要么客户端给mysql发数据,要么mysql给客户端发数据,不能同时发生

mysql存储引擎是以表为单位的

innodb内存结构

BufferPool(默认128M),ChangeBuffer(默认占25%),LogBuffer(默认16M),Adaptive Hash Index
修改数据先落ChangeBuffer,后台线程merge到磁盘,或者shutdown,或者写满时,或者数据被访问时。
先记LogBuffer,再每秒/每事务刷到redo log(主要用于崩溃恢复)。log记录是顺序IO,DB file是随机IO

double write:分内存double write和磁盘double write(顺序IO)

SSD的顺序IO性能不是很好

性能差的SSD顺序IO性能甚至比不上传统磁盘

流程图

redolog分两步:prepare状态和commit状态
先写redo log,后写binlog
undo log在redo log之前
undo log:Innodb引擎层的逻辑日志(反向SQL),用于事务回滚和MVCC
redo log:Innodb引擎层的物理日志(记录最终数据),配合checkpoint完成故障恢复和事务回滚,环形覆盖写
bin log:server层的二进制日志(逻辑日志),不断追加写
磁盘顺序IO的性能不比直接往内存写数据低

事务持久性通过redo log和double write保证

innodb中的MVCC是通过undo log实现

innodb的锁--两个行锁两个表锁

行锁

共享锁:select .. lock in share mode; -- 手动加共享锁
排它锁:增删改会自动加排它锁;select ... for update; -- 手动加排它锁

表锁

意向共享锁:行数据加共享锁之前,先给表增加一个意向共享锁
意向排它锁:行数据加排他锁之前,先给表增加一个意向排他锁
避免了后续加行锁时的数据查找,表锁的加锁性能高

行锁原理

走索引则是行锁=>锁主键索引行
不走索引会全表扫描=>表锁
注:innodb一定有索引(保底一个聚簇索引),只不过有时候不走索引

3种行锁算法

Record Lock:精确命中
Gap Lock(只在Repeatable Read中存在):查询记录不存在
Next-Key Lock(mysql默认的行锁算法):查询同时命中Record Lock和Gap Lock

死锁

如果一个事务长期持有某个锁不释放,可以kill该事务对应的线程id

innodb辅助索引为啥存主键值而不是主键地址值

页分裂会更改主键地址值

覆盖索引-避免回表

索引条件下推

频繁更新的字段不要做索引

createTime可以,但updateTime不要=>容易页分裂

连接分交互式连接和非交互式连接

Explain之Extra

Explain之type(连接类型)

const:用到主键索引或唯一索引,只有1条结果。注:如果用到索引但没有结果的话,type为空。

system:是特殊测额const,表示只有一条数据的系统表。

eq_ref:多出现在多表关联时,每个表的数据1对1,一般伴随主键索引或唯一索引出现。


示例1
加上where条件

ref:使用了普通索引(非主键/非唯一索引),或者普通索引的最左前缀。

range:普通索引碰上范围条件

index:select后边的字段刚好有索引,但是没有where条件

all:全文扫描

表优化经验

将不常用的字段拆出去
字段类型合理选择
尽量not null。设置默认值:0,空字符串,特殊值等
不要用外键、触发器、视图

分布式事务

两阶段协议XA

用的很少,1次长事务(try+commit: 数据库锁-性能差),需要数据库支持(MySQL中只有InnoDB存储引擎支持XA协议),没有超时机制(3阶段协议有)。
缺点:同步阻塞(事务协调器会被第一阶段阻塞),单点故障(事务协调器挂掉),脑裂导致数据不一致(A参与者commit,B参与者没有收到commit请求---即网络分区问题)。

三阶段协议

把二阶段的第一阶段拆成2步,先发起一次canCommit询问(超时时间很短),一定程度上解决二阶段的阻塞问题;
但是单点故障和数据不一致问题仍存在。

TCC

性能比XA好,但业务侵入性高,需要业务方提供3个接口(很多业务方不会配合)用作try-confirm-cancel,这是3次事务操作。eg:转账try接口要冻结资金(第1次事务),操作成功/失败后再掉第2/3个接口去confirm/cancel(第2次事务)

一般逻辑:尽量避免分布式事务,我把消息发给你之后,多方都必须成功,不管你是重试、报警还是人工处理

mq解决分布式事务

possible_key为空,key可能有值吗

explain之rows来源

explain之Filtered

表示存储引擎层返回的数据,被server层过滤后剩余的百分比。

explain之Extra-filesort

首先:filesort对性能影响不大,因为我们基本都是分页查找
优化:代码里做排序;适当增大sort buffer;使用索引干掉它。
索引方案(表数据量太少的话无法证明--始终走全表扫描):

深度分页--先确定id范围

如果id连续,先根据分页条件计算好id范围,然后一次性定位抓取。
如果id不连续,还是先通过子查询确定id范围或初始值,然后定位抓取(还是会全表扫描)。


如果查询很频繁的话,可以将id列表加载到redis-list
最通用方案:记录上次条件查询的id,可以不断点击下1页或者下N页,但是不能随意跳转[从第1页调到1000页]。eg:redis--key[查询条件],value[结果的起始id]

索引创建

where A = x and B > y order C => index(A,B)最佳,index(A,B,C)的话C没有用
eg:[A1, B2, C3]、[A1, B3, C2]、[A1, B3, C3] => 由于条件B是大于号,导致B是一个范围,于是C元素就是乱序了

where A = x and B = y order C => index(A,B,C)的话C有用

or条件查询需要两边都能触发索引

SELECT
    id,
    type,
    target_id,
    context,
        ...
FROM
    task force index(expect_execute_time)
WHERE
    (
        (
            `status` = 1
            AND now() >= expect_execute_time
        )
        OR (
            `status` = 2
            AND now() > timeout_time
        )
    )
AND (
    type IN (
        5000,
        200
    )
)
AND max_attempts > attempts
ORDER BY
    prior DESC,
    expect_execute_time ASC
LIMIT 100
索引

注:如果索引中把timeout_time和expect_execute_time放到status前边,那索引就会失效,因为now() > expect_execute_time和now() > timeout_time,是大于号。

索引

json字段不能建索引

操作系统Huge Page

操作系统一般默认1页4K,但是提供Huge Page(一般2M)供用户选择性的提升性能,但是这个东西不见得有效,必须经过严格的测试才能选择,选择不当甚至会降低性能。
http://cenalulu.github.io/linux/huge-page-on-numa/
思考:mysql一页16K,是否越大越好,BTree的层级就会很矮。
分析:16K一般是个折中数据,层级矮了,IO次数少了,但是每次IO的数据多了,会加载更多没意义的数据;而且在写数据的时候,锁的粒度也变大了

hash索引


如果hash碰撞严重,则性能非常低。eg:sex=男/女,这种情况的碰撞非常严重,链表很长,导致根据其他参数做equals()操作时,要遍历长链表。
此类场景使用oracle的位图索引是比较合适的:https://www.cnblogs.com/mafeng/p/7909450.html
通过位运算来获取[sex="男" and Marital="未婚"]的id。

批量插数据的脚本

DROP PROCEDURE IF EXISTS xxx;
CREATE PROCEDURE xxx ()
BEGIN
        DECLARE x BIGINT DEFAULT 0;
        declare str varchar(255) default '';
        WHILE (x < 100000) DO
                set str = substring(MD5(RAND()),1,20);
            INSERT INTO user(name, age) VALUES(concat(str, x), x);
                set x = x + 1;
        END WHILE;
END;

CALL xxx ();

json对象替换

目的:[{"propertyId": 100023}] => [{"options": {}, "propertyId": 100023}]

UPDATE property_template 
SET common_items = json_replace ( common_items, '$[0]', '{"propertyId":100023, "options" :{}}' ) 
WHERE
    id = 100005;    -- 失效:进去就变成字符串了
    
UPDATE property_template 
SET common_items = json_set ( common_items, '$[0].options', {}) 
WHERE
    id = 100005;  -- 失效
    
UPDATE property_template 
SET common_items = json_insert ( common_items, '$[0]', '{"options" :{}}' ) 
WHERE
    id = 100005;  -- 失效
    
UPDATE property_template 
SET common_items = '[{"propertyId": 100023, "options":{}}]'
WHERE
    id = 100005;  -- 成功

1000万的表删除1个索引要20分钟

order by id limit 10可能导致索引被误选id索引

注:主键索引pri_id;辅助索引id和city_id
select xx from goods where city_id = 5 order by id limit 10; => 走id索引
select xx from goods where city_id = 5 order by id; => 走city_id索引
select xx from goods where city_id = 5 order by create_time limit 10; => 走city_id索引
select xx from goods where city_id = 5 order by pri_id limit 10; => 走city_id索引

filesort

select xx from tableX where id in (1,2,3) order by create_time desc; -- Extra: Using index condition; Using filesort
select xx from tableX where id in (1,2,3) order by id desc; -- Extra: Using index condition
id数量越多,性能差距越多

index merge导致的死锁

已有索引:bussiness的单索引,city的单索引。
正常情况下,MySQL只会选择1个索引执行,但有时(可能跟数据量等条件有关)会同时使用多个


index merge:有并集(or条件),有交集(and条件)
解决方案:联合索引

mysql索引排序规则——先按索引字段再按id自增

京东到家——延迟数据库(binlog订阅主库)

目的:防止线上误删数据,用于及时救火回滚

in的数量太多可能不走索引

线上发现in (大于150个id)的时候没有走索引,但改成100个就走了索引

数据库加字段

平均15万条/分钟。
case:
1.7万的表加俩字段耗时11秒;
1400万表加7个字段耗时90分钟

limit和order by的陷阱

https://blog.csdn.net/qiubabin/article/details/70135556
分页时order by的条件如果重复,分页结果会随机重复,所以需要在order by之后额外增加自增id作为排序依据。
order by update_time desc, id desc

唯一索引问题

字段A + 字段B + 状态值(0或1)组成唯一索引,但是只能有一个1,可以有多个0

A-B-0
A-B-0
A-B-1
正确
—————
A-B-0
A-B-0
A-B-1
A-B-1
错误

思路:新增一个辅助字段X,所有状态0的数据,X字段赋值UUID;所有状态1的数据,X字段都赋相同的值

特殊查询

goods_sku表:有sku_code,six_nine_code,new_six_nine_code字段
six_nine_code_relation表:有sku_code,six_nine_code字段
目标:sku_code—six_nine_code关系,在six_nine_code_relation表是否有遗漏
步骤:

  1. 把goods_sku表的six_nine_code和new_six_nine_code打平并union
  2. 以上一步的结果为左表,left join six_nine_code_relation表
  3. 查询第二步关联表中,右表(six_nine_code_relation)的sku_code是null的数据
select
  *
from
  (
    select
      a.*,
      r.sku_code as Rsku_code,
      r.six_nine_code as Rsix_nine_code
    from
      (
        select
          sku_code,
          six_nine_code
        from
          goods_sku
        union all
        select
          sku_code,
          new_six_nine_code as six_nine_code
        from
          goods_sku
        where
          new_six_nine_code != ''
          and new_six_nine_code != six_nine_code
      ) as a
      left join six_nine_code_relation r on a.sku_code = r.sku_code and a.six_nine_code = r.six_nine_code
  ) as bb
where
  Rsku_code is  null

边执行边加锁边优化

意料外的间隙锁——容易导致翻车

普通索引:不论等值查询还是范围查询,都是间隙锁;
唯一索引:等值查询,退化为行锁;范围查询或者不存在的数据查询,都是间隙锁。
https://zhuanlan.zhihu.com/p/48269420

replace into的死锁

先delete,再insert,中间穿插其他间隙锁,就会造成死锁
https://blog.csdn.net/weixin_43277501/article/details/105753213

mysql锁

CREATE TABLE `user_test` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `age` int(3) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_name` (`name`) USING BTREE,
  UNIQUE KEY `idx_age` (`age`) USING HASH
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
事务1:
BEGIN;
SELECT * from user_test where name = '张三' for update;  //锁住了idx_name和主键索引上的id=3的数据
事务2:
BEGIN;
SELECT * from user_test where id = 3 for update;  //阻塞
事务1:
BEGIN;
SELECT name from user_test where name = '张三' for update;    
//锁住了idx_name和主键索引上的id=3的数据,虽然select name但是仍要回表锁住主键索引数据,否则主键索引被同时修改会造成数据不一致。
//或者理解成:update user_test set name = 'xxx' where name = '张三' ;  //要改两个索引,肯定要加两个锁
事务2:
BEGIN;
SELECT age from user_test where age = 23 for update;  //阻塞
事务1:
BEGIN;
SELECT * from user_test where name = '张三' for update;
事务2:
BEGIN;
SELECT * from user_test where name = '李四' for update;  //不阻塞

索引下推

在覆盖索引的基础上,为了减少回表次数,尽量将过滤条件在存储引擎层使用,而不要在服务层做大量回表和过滤。
eg:user表有first_name和last_name两个字段,二级索引:idx_userName_lastName
select * from user where first_name like 'a%' and last_name like '%b';
一般逻辑:只用到二级索引的前缀索引,返回大量数据到server层,server层大量回表和过滤。
索引下推(index condition pushdown):在存储引擎层,完成last_name的过滤

select last_insert_id()

  1. 使用Max() 函数获取主键id最大的那条记录的信息中的id。然而,再高并发的情况下,插入数据后使用Max()函数查询前又有其他数据插入这样获取的就不是想要的数据,所以不推荐这样做。
  2. 使用LAST_INSERT_ID() 这个函数也是获取最后插入的记录的id,这个函数需要和AUTO_INCREMENT 属性一起使用,当往带有AUTO_INCREMENT属性字段的表中新增记录时,LAST_INSERT_ID()即返回该字段的值。也可以带上参数使用,如:LAST_INSERT_ID(10) 则返回10
  3. 与表无关:如果向表a插入数据后,再向表b插入数据,LAST_INSERT_ID返回表b中的Id值
  4. 查询和插入所使用的Connection对象必须是同一个才可以,否则返回值是不可预料的
  5. 假如你使用一条[INSERT]语句插入多个行, LAST_INSERT_ID() 只返回插入的第一行数据时产生的值。所以留个疑问:批量插入时,如果id自增步长是2,而mybatis批量插入获取到的id默认自增步长是1,这里可能有问题

你可能感兴趣的:(数据库)