Menu

שגיאות נפוצות ב-SQLite: מסד נעול, readonly, malformed ועוד

שגיאות ה-SQLite שבאמת תיתקלו בהן בפרודקשן: database is locked, readonly database, disk image malformed, כישלונות של אילוצים, ואיך לתקן אותן.

בדף הזה יש עורכים שאפשר להריץ - לערוך, להריץ ולראות את הפלט מיד.

שגיאות הן פשוט SQLite שמנסה להגיד לכם משהו

הודעות השגיאה של SQLite קצרות ולפעמים סתומות, אבל הן מתמפות לקבוצה קטנה של בעיות בסיסיות. רוב מה שתיתקלו בו בפרודקשן נופל לחמש קטגוריות: נעילות, הרשאות, פגיעה בקובץ, אי התאמות בסכמה והפרות של אילוצים. העמוד הזה עובר על כל אחת מהן: מה גורם לה, מה היא אומרת בפועל ואיך מתקנים אותה.

מחרוזות השגיאה מגיעות יחד עם קודים מספריים (קודים מורחבים ספציפיים עוד יותר). תראו את שתי הצורות בלוגים:

Error: database is locked          -- קוד 5 (SQLITE_BUSY)
Error: unable to open database     -- קוד 14 (SQLITE_CANTOPEN)
Error: attempt to write a readonly -- קוד 8 (SQLITE_READONLY)
Error: database disk image is      -- קוד 11 (SQLITE_CORRUPT)

הכרת הקוד עוזרת בחיפוש: SQLITE_BUSY מחזיר תוצאות טובות בהרבה מהודעת השגיאה באנגלית פשוטה.

database is locked (SQLITE_BUSY)

שגיאת ה-SQLite הנפוצה ביותר בכל אפליקציה שכותבת מיותר ממקום אחד. SQLite מסדרת את הכתיבות בתור: רק חיבור אחד יכול להחזיק את נעילת הכתיבה בכל רגע. אם כותב שני לא מצליח לקבל את הנעילה בתוך זמן ההמתנה, מקבלים את השגיאה הזו.

שלושה תיקונים, לפי סדר ההשפעה:

מצב WAL לבדו פותר את בעיית הנעילה ברוב עומסי העבודה. זמן ההמתנה הוא רשת הביטחון שלכם לתחרות אמיתית על כתיבה. מעבר להגדרות, בדקו את הקוד שלכם: טרנזקציה שנשארת פתוחה בזמן שהתוכנית עושה I/O ברשת תחזיק את הנעילה כל אותו זמן. שמרו על טרנזקציות קצרות, ובצעו COMMIT (או ROLLBACK) ברגע שהעבודה הסתיימה.

unable to open database file (SQLITE_CANTOPEN)

SQLite ניסתה לפתוח את הקובץ ומערכת ההפעלה סירבה. ב-95% מהמקרים הבעיה היא נתיב הקובץ או התיקייה שלו:

-- מה לבדוק:
-- 1. האם הנתיב קיים?                ls -l /path/to/db.sqlite
-- 2. האם תיקיית האב קיימת? SQLite יוצרת את הקובץ
--    אבל לא את התיקייה שמעליו.
-- 3. האם למשתמש שמריץ את התהליך שלכם יש הרשאת קריאה+כתיבה
--    על התיקייה (לא רק על הקובץ)?
-- 4. האם הכונן מחובר, לא מלא, ולא לקריאה בלבד?

מקרה עדין: SQLite צריכה ליצור קבצים נלווים (-journal, -wal, -shm) ליד מסד הנתונים. אם אפשר לכתוב לקובץ עצמו אבל לא לתיקייה, הפתיחה מצליחה והכתיבות נכשלות. תנו תמיד הרשאת כתיבה ברמת התיקייה.

attempt to write a readonly database (SQLITE_READONLY)

בן דוד קרוב של הקודמת. הקובץ נפתח בסדר אבל הכתיבות נכשלות. הסיבות, לפי שכיחות:

  • למשתמש מערכת ההפעלה אין הרשאת כתיבה על הקובץ או על התיקייה שלו.
  • החיבור נפתח עם דגל לקריאה בלבד (SQLITE_OPEN_READONLY, או mode=ro ב-URI).
  • הכונן מחובר לקריאה בלבד (נפוץ ב-bind mounts של Docker ובחלק ממערכות הקבצים בענן).
  • מסד הנתונים נמצא על מערכת קבצים ברשת שלא תומכת בנעילות ש-SQLite צריכה.

תקנו את ההרשאות או חברו מחדש את הכונן. אם אתם ב-Docker, ודאו שה-bind mount אינו :ro ושמשתמש הקונטיינר הוא הבעלים של התיקייה.

database disk image is malformed (SQLITE_CORRUPT)

הבייטים של הקובץ כבר לא תואמים את הפורמט של SQLite. הסיבות האמיתיות הן בדרך כלל סביבתיות: תהליכים שנהרגו באמצע כתיבה במערכות קבצים בלי fsync תקין, העתקת מסד הנתונים בזמן שכותב היה פעיל, תקלות חומרה, או סנכרון הקובץ דרך Dropbox/iCloud.

קודם כול, אשרו את הנזק:

אם integrity_check מחזיר ok, מסד הנתונים שלכם תקין והשגיאה הגיעה ממקום אחר (לעיתים קרובות חיבור ישן). אם הוא מחזיר רשימת בעיות, צריך לשחזר.

דרך השחזור הנקייה ביותר משתמשת בפקודה .recover של ה-CLI, שמחלצת כל נתון שהיא יכולה למסד נתונים חדש:

sqlite3 corrupt.db ".recover" | sqlite3 recovered.db
sqlite3 recovered.db "PRAGMA integrity_check;"

אם יש לכם גיבוי עדכני, שחזרו ממנו במקום זה: זה מהיר יותר ומונע את העמימות של "הצלחנו להחזיר את רוב הנתונים". ראו את העמוד על גיבוי ושחזור כדי לדעת מה הדרך הנכונה להעתיק מסד נתונים חי (רמז: לא cp).

no such table ו-no such column

הן אומרות בדיוק את מה שהן אומרות, אבל הסיבה היא בדרך כלל אחת משתיים: אתם מחוברים למסד נתונים אחר ממה שאתם חושבים, או שמיגרציה לא רצה.

בדקו את מחרוזת החיבור של האפליקציה: נתיבים יחסיים מחושבים ביחס לתיקיית העבודה הנוכחית, שמשתנה בין הטרמינל, סביבת הפיתוח ותהליך הפרודקשן. מסד נתונים בזיכרון (:memory:) חדש בכל פעם, וזה מפתיע אנשים שמצפים שהנתונים יישמרו.

גם מירכאות סביב מזהים חשובות. שמות בלי מירכאות לא מבחינים בין אותיות גדולות לקטנות, אבל "User" ו-"user" הם מזהים שונים. אם יצרתם טבלה עם מירכאות סביב השם, תצטרכו להמשיך לשים אותן.

הפרות של אילוצים

SQLite מסרבת לכתיבות שהיו שוברות אילוץ. הודעת השגיאה מציינת איזה מהם:

כל כישלון הוא קוד שונה מאחורי הקלעים (SQLITE_CONSTRAINT_UNIQUE, SQLITE_CONSTRAINT_CHECK, SQLITE_CONSTRAINT_NOTNULL). התיקון כמעט תמיד נמצא בשכבת האפליקציה: אמתו את הקלט לפני הכתיבה, או השתמשו ב-INSERT ... ON CONFLICT כדי לטפל בכפילויות באופן מכוון.

FOREIGN KEY constraint failed ראויה להערה משלה: מפתחות זרים כבויים כברירת מחדל ב-SQLite. אם לא מפעילים אותם, הפניות לא תקינות נכנסות בשקט, ונכשלות מאוחר יותר כשסוף סוף מפעילים את האכיפה. הגדירו את ה-pragma בכל חיבור:

cannot start a transaction within a transaction

קראתם ל-BEGIN כשטרנזקציה כבר הייתה פתוחה. SQLite לא מאפשרת טרנזקציות מקוננות, אבל היא כן מאפשרת savepoints מקוננים, שנותנים את אותה תוצאה:

אם ה-ORM או הפריימוורק שלכם מנהלים טרנזקציות, כנראה ביקשתם מהם להתחיל אחת פעמיים. בדקו אם autocommit פעיל, והאם מאגר החיבורים שלכם משתמש שוב בחיבור שכבר יש בו טרנזקציה פתוחה.

disk I/O error (SQLITE_IOERR)

מערכת ההפעלה דחתה קריאה או כתיבה. דיסק מלא, תקלה רגעית במערכת קבצים ברשת, או שהקובץ נמחק מתחת לרגליים של SQLite. הדבר הראשון לבדוק הוא df -h. השני הוא אם מסד הנתונים יושב על משהו לא יציב כמו NFS או תיקייה שמסונכרנת לענן: SQLite מניחה מערכת קבצים מקומית מסוג POSIX עם fsync שעובד. אם אי אפשר להעביר אותו, קבלו את זה שהסיכון לפגיעה בקובץ עולה.

syntax error near "..."

המפענח של SQLite אומר לכם איזה אסימון בלבל אותו. התיקון נמצא בדרך כלל שלוש שורות לפני המקום שהשגיאה מצביעה עליו: פסיק חסר, מזהה בלי מירכאות שמתנגש עם מילת מפתח, או מחרוזת עם גרשיים בודדים שצריך לברוח מהם ('it''s', לא 'it's').

השתמשו בקשירת פרמטרים (מצייני מקום ?) לקלט של משתמשים במקום לבנות SQL בשרשור מחרוזות: כך תתחמקו בבת אחת מקטגוריה שלמה של שגיאות תחביר ומ-SQL injection.

רשימת בדיקה לאבחון

כשמשהו נשבר בפרודקשן, הרצף הזה מכסה את רוב המקרים בפחות מדקה:

חמש פקודות pragma, חמש תשובות. יחד עם קוד השגיאה מהשאילתה שנכשלה, תדעו לאיזו קטגוריה הבעיה שייכת ואיזה עמוד בתיעוד לפתוח עכשיו.

סיכום תוכנית הלימודים

זה הסיור. עברתם מ-CREATE TABLE דרך JOIN, אינדקסים, טרנזקציות, מצב WAL וגיבויים, ועכשיו גם דרך דפוסי הכישלון שמופיעים כש-SQLite פוגשת את העולם האמיתי. הדפוסים חוזרים על עצמם: טרנזקציות קצרות, מפתחות זרים מופעלים, מצב WAL, גיבויים קבועים וכבוד בריא ל-PRAGMA integrity_check. שמרו על ההרגלים האלה ו-SQLite תרוץ בשקט במשך שנים.

שאלות נפוצות

למה SQLite אומרת 'database is locked'?

חיבור אחר מחזיק נעילת כתיבה, והחיבור שלכם הגיע לזמן הקצוב בזמן שחיכה. התיקונים הרגילים הם הפעלת מצב WAL עם PRAGMA journal_mode=WAL כדי שקוראים לא יחסמו כותבים, הגדלת זמן ההמתנה עם PRAGMA busy_timeout = 5000, ולוודא שאתם מבצעים commit לטרנזקציות מהר במקום להשאיר אותן פתוחות.

איך מתקנים את 'attempt to write a readonly database' ב-SQLite?

כמעט תמיד זו בעיה של הרשאות במערכת הקבצים, לא של SQLite. משתמש מערכת ההפעלה שמריץ את התהליך שלכם צריך הרשאת כתיבה גם לקובץ מסד הנתונים וגם לתיקייה שמכילה אותו (SQLite יוצרת שם קבצים נלווים מסוג -journal או -wal). בדקו בעלות, ביטי הרשאות, ושהכונן לא מחובר לקריאה בלבד.

מה המשמעות של 'database disk image is malformed'?

SQLite קראה בייטים שלא תואמים את הפורמט שהיא מצפה לו, בדרך כלל בגלל פגיעה בקובץ שנגרמה מתהליכים שנהרגו, דיסקים תקולים או העתקת הקובץ בזמן שהיה פתוח. הריצו PRAGMA integrity_check כדי לאשר, ואז שחזרו עם .recover ב-CLI כדי לשפוך את מה שאפשר להציל למסד נתונים חדש. אם יש לכם גיבוי, שחזור ממנו מהיר יותר.

למה אני מקבל 'no such table' או 'no such column'?

אתם מחוברים לקובץ מסד נתונים אחר ממה שאתם חושבים, או שמיגרציה לא רצה. בדקו את PRAGMA database_list כדי לראות את נתיב הקובץ ש-SQLite באמת פתחה, ואת .schema tablename כדי לראות את העמודות האמיתיות. גם שגיאות כתיב ואי התאמה של אותיות גדולות וקטנות במזהים נפוצות: SQLite לא מבחינה בין אותיות גדולות לקטנות בשמות בלי מירכאות, אבל כן מבחינה בשמות עם מירכאות.

איור של שפות התכנות ב-Coddy

ללמוד תכנות עם Coddy

להתחיל