工作经验
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踩坑
乐观锁
两种乐观锁:
基于版本号或时间戳(永远自增的)实现。
缺点:电商抢购活动时,大量并发进入,会出现大量的用户查询出库存存在,但是却在扣减库存时失败了
update t_goods set version=version+1 where id=#{id} and version=#{version};
- 基于条件限制实现[库存数量等条件]
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(*)性能更好
索引失效场景
- like “%xxx”
- 数据类型隐式转换。varchar字段用作数字查询
- 不满足最左前缀匹配原则
- 取反操作:not null,!=,<>
- 走全表扫描比走索引快的时候
- or左右表达式都要触发索引
- 索引字段做了运算
阿里开发手册禁用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,一般伴随主键索引或唯一索引出现。
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表是否有遗漏
步骤:
- 把goods_sku表的six_nine_code和new_six_nine_code打平并union
- 以上一步的结果为左表,left join six_nine_code_relation表
- 查询第二步关联表中,右表(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()
- 使用Max() 函数获取主键id最大的那条记录的信息中的id。然而,再高并发的情况下,插入数据后使用Max()函数查询前又有其他数据插入这样获取的就不是想要的数据,所以不推荐这样做。
- 使用LAST_INSERT_ID() 这个函数也是获取最后插入的记录的id,这个函数需要和AUTO_INCREMENT 属性一起使用,当往带有AUTO_INCREMENT属性字段的表中新增记录时,LAST_INSERT_ID()即返回该字段的值。也可以带上参数使用,如:LAST_INSERT_ID(10) 则返回10
- 与表无关:如果向表a插入数据后,再向表b插入数据,LAST_INSERT_ID返回表b中的Id值
- 查询和插入所使用的Connection对象必须是同一个才可以,否则返回值是不可预料的
- 假如你使用一条[INSERT]语句插入多个行, LAST_INSERT_ID() 只返回插入的第一行数据时产生的值。所以留个疑问:批量插入时,如果id自增步长是2,而mybatis批量插入获取到的id默认自增步长是1,这里可能有问题