MySQL索引失效的八大常見場景及解決方法
一、索引失效的"元兇"TOP 8
1. 函數(shù)操作導(dǎo)致索引失效
錯(cuò)誤案例:
-- 對索引列使用函數(shù)導(dǎo)致全表掃描 SELECT * FROM orders WHERE DATE(create_time) = '2023-01-01'; -- 即使create_time有索引也會失效
執(zhí)行計(jì)劃:
type: ALL (全表掃描) key: NULL (未使用索引)
Java優(yōu)化方案:
// 使用范圍查詢替代函數(shù)操作
@Query("SELECT o FROM Order o WHERE o.createTime >= :startDate AND o.createTime < :endDate")
List<Order> findByDateRange(@Param("startDate") LocalDateTime start,
@Param("endDate") LocalDateTime end);
2. 隱式類型轉(zhuǎn)換
錯(cuò)誤案例:
-- 字符串與數(shù)字比較導(dǎo)致索引失效 SELECT * FROM users WHERE phone = 13800138000; -- phone是VARCHAR類型
執(zhí)行計(jì)劃:
type: ALL (全表掃描) key: NULL (未使用索引)
Java優(yōu)化方案:
// 確保參數(shù)類型與數(shù)據(jù)庫字段類型一致
@Query("SELECT u FROM User u WHERE u.phone = :phone")
User findByPhone(@Param("phone") String phone); // 使用String而非Long
3. OR條件濫用
錯(cuò)誤案例:
-- OR條件導(dǎo)致索引失效 SELECT * FROM products WHERE category_id = 1 OR price > 1000; -- 即使category_id有索引也會失效
執(zhí)行計(jì)劃:
type: ALL (全表掃描) key: NULL (未使用索引)
Java優(yōu)化方案:
// 使用UNION ALL替代OR條件
@Query("SELECT p FROM Product p WHERE p.categoryId = :categoryId " +
"UNION ALL " +
"SELECT p FROM Product p WHERE p.price > :price AND p.categoryId != :categoryId")
List<Product> findByCategoryOrPrice(@Param("categoryId") Long categoryId,
@Param("price") BigDecimal price);
4. NOT IN/!=/<> 操作
錯(cuò)誤案例:
-- NOT IN導(dǎo)致索引失效 SELECT * FROM orders WHERE status NOT IN (1, 2, 3); -- 即使status有索引也會失效
執(zhí)行計(jì)劃:
type: ALL (全表掃描) key: NULL (未使用索引)
Java優(yōu)化方案:
// 使用LEFT JOIN + IS NULL替代NOT IN
@Query("SELECT o FROM Order o " +
"LEFT JOIN OrderStatus os ON o.status = os.id AND os.id IN (1,2,3) " +
"WHERE os.id IS NULL")
List<Order> findByStatusNotIn(@Param("statusList") List<Integer> statusList);
5. 復(fù)合索引違反最左前綴
錯(cuò)誤案例:
-- 創(chuàng)建復(fù)合索引 (user_id, status, create_time) ALTER TABLE orders ADD INDEX idx_user_status_time (user_id, status, create_time); -- 查詢未使用最左前綴導(dǎo)致索引失效 SELECT * FROM orders WHERE status = 1 AND create_time > '2023-01-01'; -- 缺少user_id條件
執(zhí)行計(jì)劃:
type: ALL (全表掃描) key: NULL (未使用索引)
Java優(yōu)化方案:
// 確保查詢條件包含復(fù)合索引的最左前綴
@Query("SELECT o FROM Order o WHERE o.userId = :userId AND o.status = :status " +
"AND o.createTime > :startTime")
List<Order> findByUserStatusAndTime(@Param("userId") Long userId,
@Param("status") Integer status,
@Param("startTime") LocalDateTime startTime);
6. LIKE查詢以通配符開頭
錯(cuò)誤案例:
-- LIKE '%keyword%'導(dǎo)致索引失效 SELECT * FROM articles WHERE title LIKE '%MySQL%'; -- 即使title有索引也會失效
執(zhí)行計(jì)劃:
type: ALL (全表掃描) key: NULL (未使用索引)
Java優(yōu)化方案:
// 使用全文索引替代LIKE模糊查詢
@Entity
@Table(indexes = {
@Index(name = "idx_title_fulltext", columnList = "title",
type = IndexType.FULLTEXT) // MySQL 5.6+支持
})
public class Article {
// ...
}
// 查詢示例
@Query(value = "SELECT a FROM Article a WHERE MATCH(a.title) AGAINST(:keyword IN BOOLEAN MODE)",
nativeQuery = true)
List<Article> searchByKeyword(@Param("keyword") String keyword);
7. 索引列參與計(jì)算
錯(cuò)誤案例:
-- 索引列參與計(jì)算導(dǎo)致失效 SELECT * FROM users WHERE YEAR(birthday) = 1990; -- 即使birthday有索引也會失效
執(zhí)行計(jì)劃:
type: ALL (全表掃描) key: NULL (未使用索引)
Java優(yōu)化方案:
// 將計(jì)算邏輯移到Java端或使用范圍查詢
@Query("SELECT u FROM User u WHERE u.birthday >= :start AND u.birthday < :end")
List<User> findByBirthYear(@Param("start") LocalDate start,
@Param("end") LocalDate end);
// 調(diào)用示例
LocalDate start = LocalDate.of(1990, 1, 1);
LocalDate end = LocalDate.of(1991, 1, 1);
List<User> users = userRepository.findByBirthYear(start, end);
8. 數(shù)據(jù)分布不均導(dǎo)致索引失效
錯(cuò)誤案例:
-- 性別字段(區(qū)分度極低)即使有索引也會失效 SELECT * FROM users WHERE gender = 'M'; -- 假設(shè)男女比例接近1:1
執(zhí)行計(jì)劃:
type: ALL (全表掃描) key: NULL (優(yōu)化器選擇全表掃描)
Java優(yōu)化方案:
// 避免為低區(qū)分度字段建索引
// 或改用其他高區(qū)分度條件
@Query("SELECT u FROM User u WHERE u.gender = :gender AND u.status = :status")
List<User> findByGenderAndStatus(@Param("gender") String gender,
@Param("status") Integer status);
二、索引失效的"診斷工具箱"
2.1 EXPLAIN命令深度解析
EXPLAIN SELECT * FROM orders WHERE user_id = 12345;
關(guān)鍵字段說明:
type:訪問類型(ALL=全表掃描,index=索引掃描,range=范圍掃描,ref=索引引用)key:實(shí)際使用的索引rows:預(yù)估需要檢查的行數(shù)Extra:額外信息(Using index=覆蓋索引,Using where=需回表)
2.2 Java中的慢查詢監(jiān)控
// Spring Boot配置示例(application.properties)
spring.datasource.hikari.connection-test-query=SELECT 1
spring.jpa.properties.hibernate.generate_statistics=true
spring.jpa.properties.hibernate.session.events.log.LOG_QUERIES_SLOWER_THAN_MS=100
// 自定義攔截器記錄慢查詢
@Component
public class SlowQueryInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
long startTime = System.currentTimeMillis();
request.setAttribute("startTime", startTime);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
long startTime = (Long) request.getAttribute("startTime");
long duration = System.currentTimeMillis() - startTime;
if (duration > 500) { // 記錄超過500ms的查詢
logger.warn("Slow query detected: {}ms, URL: {}",
duration, request.getRequestURI());
}
}
}
三、索引優(yōu)化最佳實(shí)踐
3.1 索引設(shè)計(jì)三原則
選擇性原則:優(yōu)先為區(qū)分度高的列建索引(如用戶ID、訂單號)
復(fù)合索引順序:高頻查詢條件放前面,范圍查詢條件放最后
-- 正確示例:先等值查詢,后范圍查詢 ALTER TABLE orders ADD INDEX idx_user_status_time (user_id, status, create_time);
- 覆蓋索引優(yōu)化:讓查詢完全通過索引獲取數(shù)據(jù)
-- 優(yōu)化前 SELECT user_id, order_no FROM orders WHERE user_id = 12345; -- 優(yōu)化后(添加order_no到復(fù)合索引) ALTER TABLE orders ADD INDEX idx_user_order (user_id, order_no);
3.2 Java代碼中的索引保護(hù)
// 使用@Query注解強(qiáng)制使用索引(MySQL 5.7+)
@Query(value = "SELECT * FROM orders FORCE INDEX(idx_user_status_time) " +
"WHERE user_id = :userId AND status = :status",
nativeQuery = true)
List<Order> findByUserIdAndStatus(@Param("userId") Long userId,
@Param("status") Integer status);
// 分頁查詢優(yōu)化(避免大偏移量)
public interface OrderRepository extends JpaRepository<Order, Long> {
@Query("SELECT o FROM Order o WHERE o.userId = :userId " +
"AND (o.createTime < :lastCreateTime OR " +
"(o.createTime = :lastCreateTime AND o.id < :lastId)) " +
"ORDER BY o.createTime DESC, o.id DESC")
List<Order> findAfterCursor(@Param("userId") Long userId,
@Param("lastCreateTime") Date lastCreateTime,
@Param("lastId") Long lastId,
Pageable pageable);
}
四、總結(jié)與避坑指南
4.1 索引失效"三板斧"診斷法
- 執(zhí)行計(jì)劃分析:通過EXPLAIN確認(rèn)是否使用了預(yù)期的索引
- 數(shù)據(jù)類型檢查:確保Java參數(shù)類型與數(shù)據(jù)庫字段類型匹配
- SQL改寫測試:對可疑SQL進(jìn)行等價(jià)改寫并對比性能
4.2 常見誤區(qū)
- 索引越多越好(導(dǎo)致寫入性能下降)
- 為所有查詢條件建索引(浪費(fèi)存儲空間)
- 依賴ORM框架自動生成SQL(可能生成低效SQL)
4.3 終極建議
"先診斷,后優(yōu)化"原則:通過慢查詢?nèi)罩?、EXPLAIN和性能監(jiān)控工具定位問題,再結(jié)合業(yè)務(wù)場景選擇最優(yōu)的索引方案。
通過本文的系統(tǒng)性講解,Java開發(fā)者可以掌握MySQL索引失效的核心原因和解決方案。在實(shí)際項(xiàng)目中,建議結(jié)合A/B測試驗(yàn)證優(yōu)化效果,讓系統(tǒng)性能再上新臺階!
以上就是MySQL索引失效的八大常見場景及解決方法的詳細(xì)內(nèi)容,更多關(guān)于MySQL索引失效場景的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
MySQL事務(wù)的基礎(chǔ)學(xué)習(xí)以及心得分享
本篇內(nèi)容是關(guān)于MySQL事務(wù)的基礎(chǔ)知識學(xué)習(xí)內(nèi)容,并把學(xué)習(xí)中網(wǎng)友的心得做了總結(jié),分享給大家,一起學(xué)習(xí)參考下吧。2017-12-12
mysql開啟遠(yuǎn)程連接(mysql開啟遠(yuǎn)程訪問)
開啟MYSQL遠(yuǎn)程連接權(quán)限的方法,大家參考使用吧2013-12-12
MySQL誤刪后使用binlog恢復(fù)數(shù)據(jù)的實(shí)現(xiàn)方法
這篇文章主要介紹了MySQL誤刪后使用binlog恢復(fù)數(shù)據(jù)的實(shí)現(xiàn)方法,使用 binlog 恢復(fù)數(shù)據(jù)的預(yù)期效果是將誤刪的數(shù)據(jù)還原到誤刪之前的狀態(tài),以減少或消除數(shù)據(jù)丟失的影響,文中有相關(guān)的代碼示例和圖文介紹,需要的朋友可以參考下2024-05-05
5個(gè)保護(hù)MySQL數(shù)據(jù)倉庫的小技巧
這篇文章主要為大家詳細(xì)介紹了五個(gè)小技巧,告訴你如何保護(hù)MySQL數(shù)據(jù)倉庫,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08
MySQL執(zhí)行SQL文件報(bào)錯(cuò):Unknown collation ‘utf8mb4_0900_ai_
這篇文章主要給大家分享了MySQL執(zhí)行SQL文件出現(xiàn)【Unknown collation ‘utf8mb4_0900_ai_ci‘】的解決方案,如果又遇到相同問題的同學(xué),可以參考閱讀本文2023-09-09
MySQL實(shí)戰(zhàn)之Insert語句的使用心得
這篇文章主要給大家介紹了關(guān)于MySQL實(shí)戰(zhàn)之Insert語句的使用心得的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
mysql使用insert into select插入查出的數(shù)據(jù)
這篇文章主要介紹了mysql使用insert into select插入查出的數(shù)據(jù)方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12

